验证 Ecto "many-to-many" 关系

Validating Ecto "many-to-many" relationships

我正在尝试确定在 Ecto 2 中验证多对多关系的正确方法。我有一个需要有很多成员的对话模型,并且用户可以是许多对话的一部分,所以我像这样建立模型:

# User Model
defmodule MyApp.User do

  ...

  schema "users" do
    ....

    many_to_many :conversations, Conversation, join_through: "conversations_users"

    ...
  end

  ...
end

# Conversation Model
defmodule MyApp.Conversation do
  ...

  schema "conversations" do
    has_many :messages, Message
    many_to_many :members, User, join_through: "conversations_users"

    timestamps()
  end

  def changeset(struct, _params) do
    struct
    |> validate_member_count
  end   

  defp validate_member_count(changeset) do
    members = Repo.all(assoc(changeset, :members))
    valid? = length(members) == 2

    if valid? do
      add_error(changeset, :members, "foo")
    else
      changeset
    end
  end
end

但是,我无法让它工作。我已经编写了一个简单的测试来验证验证 运行 是否正确,但我不断收到以下错误:

# Test
test "fails to validate a conversation with less than two members" do
  changeset = Conversation.changeset(%Conversation{}, %{})

  {message, []} = changeset.errors[:members]
  assert message === "must have at least two members"
end

** (FunctionClauseError) no function clause matching in Ecto.Changeset.add_error/4

我很难理解我做错了什么。好像找不到这个函数,但是我查了文档,好像 Ecto.Changeset.add_error/4 肯定是对的,而且它的参数似乎也是正确的。

我最好的猜测是我需要在调用我的自定义验证器之前在验证中做一些事情,但我只是不知道我应该做什么。

有2个错误:

  1. 您将 MyApp.Conversation 传递给 validate_member_count,而不是 Ecto.Changeset。您可以使用 Ecto.Changeset.change/1:

    将定义 Struct 的 Ecto Schema 转换为 Ecto.Changeset
    def changeset(struct, _params) do
      struct
      |> change
      |> validate_member_count
    end
    
  2. Ecto.assoc/2 接受 Ecto Schema 结构,而不是 Ecto.Changeset。您可以使用 .data:

    Ecto.Changeset 访问底层结构
    members = Repo.all(assoc(changeset.data, :members))
    

最终代码:

def changeset(struct, _params) do
  struct
  |> change
  |> validate_member_count
end

defp validate_member_count(changeset) do
  members = Repo.all(assoc(changeset.data, :members))
  valid? = length(members) == 2

  if valid? do
    add_error(changeset, :members, "foo")
  else
    changeset
  end
end