【问题标题】:Conditional Validation in Ecto for OR - 1 of 2 fields is required在 Ecto 中对 OR 进行条件验证 - 需要 2 个字段中的 1 个
【发布时间】:2017-02-13 19:54:53
【问题描述】:

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

因此,例如,如果我想检查以确保 emailmobile 字段已填写...我希望能够将列表传递到 validate_required_inclusionfields 到验证列表中至少有 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


    【解决方案1】:

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

    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>
    

    【讨论】:

    • 读者注意您的present? 逻辑可能不同。例如,当前的实现会认为存在一个空列表。
    • 我会添加String.trim_leading(str) != "",因为所需验证的默认选项是修剪字符串值。
    【解决方案2】:

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

    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
    

    【讨论】:

      【解决方案3】:

      怎么样:

        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?将确保其中至少有一个字段。

      【讨论】:

      • 我喜欢这个,因为它比另一个短,尽管它不能放入它...我将不得不玩它。谢谢!
      • 如果字段已经在模型中而不是更改中,这将不起作用,例如Post.changeset(%Post{content: "foo"}, %{}) 将失败,因为 changes 为空,即使 content 存在。
      • 感谢@Dogbert,很好。我已经更新了答案,但它仍然不会像你的那样检查空字段。
      猜你喜欢
      • 2020-08-20
      • 1970-01-01
      • 1970-01-01
      • 2019-10-22
      • 2021-10-20
      • 2018-08-30
      • 1970-01-01
      • 1970-01-01
      • 2019-07-06
      相关资源
      最近更新 更多