Conditional Validation in Ecto for OR - 1 of 2 fields is required

前端 未结 3 1351
时光取名叫无心
时光取名叫无心 2021-02-14 02:24

How can I do conditional validation for OR logic, where we check to see if 1 of the 2 values is present or both values are present.

So, for example, if I want to check t

相关标签:
3条回答
  • 2021-02-14 03:02

    You could also create the constraint in the database, e.g. by writing a migration:

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

    And use check_constraint to validate the changeset:

    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 讨论(0)
  • 2021-02-14 03:19

    How about:

      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 gives you fields accepted by the change set, both changed (cast) and non-changed, and Enum.any? will ensure that at least one of the field is in there.

    0 讨论(0)
  • 2021-02-14 03:22

    Here's a simple way. You can customize it to support better error messages:

    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
    

    Test with a Post model and |> 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>
    
    0 讨论(0)
提交回复
热议问题