如何使用 Ecto 添加互斥约束?

How can I add a mutual exclusion constraint with Ecto?

我有一个包含两个字段的架构,ab,我希望其中一个字段是必需的。也就是说,如果提供 a,则不应提供 b,反之亦然。

有没有办法用 Ecto 变更集验证优雅地表示这一点?像这样:

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

  timestamps()
end

def changeset(transaction, attrs) do
  transaction
  |> cast(attrs, [:a, :b, :c])
  |> validate_required([:c])
  |> validate_mutual_exclusion([:a, :b])
end

defp validate_mutual_exclusion(changeset, fields) do
  # What goes here?
end

您可以计算存在的字段数并检查它是否等于 1:

defp validate_mutual_exclusion(changeset, fields) do
  present = Enum.count(fields, fn field -> present?(get_field(changeset, field)) end)

  case present do
    1 -> changeset # ok
    _ ->
      # add an error to each field
      Enum.reduce(fields, changeset, fn field, changeset ->
        add_error(changeset, field, "exactly one of these must be present: #{inspect(fields)}")
      end)
  end
end

present? 只是检查值是 "" 还是 nil:

def present?(nil), do: false
def present?(""), do: false
def present?(_), do: true