You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Ecto Changeset为Schema添加字段互斥约束?

How to Enforce Mutual Exclusion Between Fields in an Ecto Changeset

Absolutely! You can cleanly implement this exact requirement — where exactly one of a or b must be provided (no both, no neither) alongside a mandatory c field — using a custom Ecto validation function. This keeps your changeset logic readable and reusable.

Here's the complete, working implementation:

defmodule MyApp.Foo do
  use Ecto.Schema
  import Ecto.Changeset

  schema "foo" do
    field(:a, :string)
    field(:b, :string)
    field(:c, :string)
    timestamps()
  end

  @doc false
  def changeset(foo, attrs) do
    foo
    |> cast(attrs, [:a, :b, :c])
    |> validate_required([:c], message: "is required")
    |> validate_mutual_exclusion([:a, :b])
  end

  defp validate_mutual_exclusion(changeset, [field1, field2]) do
    # Get values, accounting for both existing struct values and new changes
    value1 = get_field(changeset, field1)
    value2 = get_field(changeset, field2)

    cond do
      # Both fields have non-empty values: invalid
      not is_nil(value1) and value1 != "" and not is_nil(value2) and value2 != "" ->
        changeset
        |> add_error(field1, "cannot be used together with #{field2}")
        |> add_error(field2, "cannot be used together with #{field1}")

      # Neither field has a non-empty value: invalid
      (is_nil(value1) or value1 == "") and (is_nil(value2) or value2 == "") ->
        changeset
        |> add_error(field1, "either #{field1} or #{field2} must be provided")
        |> add_error(field2, "either #{field1} or #{field2} must be provided")

      # Exactly one field has a value: valid
      true ->
        changeset
    end
  end
end

Key Details Explained:

  • Custom Validation Function: validate_mutual_exclusion/2 is a private helper that handles the core logic. It’s reusable across any other changesets that need this "exactly one of two fields" rule.
  • Value Checking: We use get_field/2 instead of fetch_change/2 because we need to account for both new values from the attrs and existing values on the struct (if you’re updating an existing record).
  • Conditional Logic:
    • Rejects cases where both a and b are filled in, adding clear errors to both fields.
    • Rejects cases where neither a nor b are filled in, again adding clear errors.
    • Passes through changesets where exactly one of the fields is present.

Adaptations for Other Field Types:

If a or b are non-string types (e.g., integers, booleans), adjust the value checks to match:

  • For integers: Replace value1 != "" with value1 != 0 (or whatever your "empty" value is).
  • For booleans: Just check not is_nil(value1) since booleans are either true, false, or nil.

内容的提问来源于stack exchange,提问作者jonnystoten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:29:55