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



我正在尝试确定在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)在Ecto.Changeset.add_error/4中没有匹配的函数子句

我很难理解我做错了什么。似乎找不到函数,但我已经检查了文档,似乎Ecto.Changeset.add_error/4绝对是正确的,并且它的参数似乎也是正确的。

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

有两个错误:

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

    将Ecto模式定义结构转换为Ecto.Changeset
    def changeset(struct, _params) do
      struct
      |> change
      |> validate_member_count
    end
    
  2. Ecto.assoc/2接受Ecto Schema Struct,而不是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

最新更新