ecto cast_assoc 查找或创建

Ben*_*ith 5 elixir ecto phoenix-framework

调用 时cast_assoc,我想要“查找或创建”样式的行为。在 Ecto 中是否有一种简单的方法可以做到这一点?

作为一个人为的例子,一个user belongs_to最喜欢的color,它是uniqueby color.name。如果我使用已经存在的最喜欢的颜色创建一个新用户,我会收到一个错误 ( name has already been taken)。相反,我想设置user.color_id为预先存在的color记录。Ecto 中是否有内置功能可以执行此操作,还是我必须推出自己的解决方案?

用户变更集:

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:name])
  |> cast_assoc(:color)
end
Run Code Online (Sandbox Code Playgroud)

失败测试:

test "create user with pre-existing color" do
  Repo.insert!(%Color{name: "red"})

  %User{}
  |> User.changeset(%{name: "Jim", color: %{name: "red"}})
  |> Repo.insert!
end
Run Code Online (Sandbox Code Playgroud)

gmi*_*ile 1

按照你的说法,恐怕你必须滚动自己的代码:在提供的示例中,你正在处理子关联(用户),尝试管理它的父级(例如颜色)。这必须手动完成。

不过要添加的代码量实际上并没有那么大。创建用户的代码如下所示:

color = Repo.get_by(Color, name: params["color"]["name"])

if color do
  %User{}
  |> User.changeset(params)
  |> Ecto.Changeset.put_assoc(:color, color)
else
  %User{}
  |> User.changeset(params)
  |> Ecto.Changeset.cast_assoc(:color)
end
|> Repo.insert!
Run Code Online (Sandbox Code Playgroud)

或者,您应该扭转您的方法 - 如果您color提前知道存在(我相信您应该这样做),您可以这样做:

color = Repo.get_by(Color, name: params["color"]["name"])

color
|> build_assoc(:user)
Run Code Online (Sandbox Code Playgroud)

这当然需要在其模式中声明颜色has_one :user, Userhas_many :users, User关联。