如何使用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/2is 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/2instead offetch_change/2because 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
aandbare filled in, adding clear errors to both fields. - Rejects cases where neither
anorbare filled in, again adding clear errors. - Passes through changesets where exactly one of the fields is present.
- Rejects cases where both
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 != ""withvalue1 != 0(or whatever your "empty" value is). - For booleans: Just check
not is_nil(value1)since booleans are eithertrue,false, ornil.
内容的提问来源于stack exchange,提问作者jonnystoten
相关产品推荐
相关产品推荐

