【发布时间】:2019-11-06 09:05:09
【问题描述】:
我正在使用 Elixir & Phoenix。我有 Venues.Team 和 Accounts.Job 架构,它们之间有 many-to-many 关系。
defmodule Runbook.Venues.Team do
use Ecto.Schema
import Ecto.Changeset
schema "teams" do
field :name, :string
belongs_to :venue, Runbook.Venues.Venue
many_to_many :employees, Runbook.Accounts.Job, join_through: "jobs_teams"
timestamps()
end
@doc false
def changeset(team, attrs) do
team
|> cast(attrs, [:name])
|> validate_required([:name])
end
end
和
defmodule Runbook.Accounts.Job do
use Ecto.Schema
import Ecto.Changeset
schema "jobs" do
field :role, :string
belongs_to :user, Runbook.Accounts.User
belongs_to :venue, Runbook.Venues.Venue
many_to_many :teams, Runbook.Venues.Team, join_through: "jobs_teams"
timestamps()
end
@doc false
def changeset(job, attrs) do
job
|> cast(attrs, [:role])
|> validate_required([:role])
end
end
我有默认的Venues.update_team/2 方法:
def update_team(%Team{} = team, attrs) do
team
|> Team.changeset(attrs)
|> Repo.update()
end
我希望在更新Team 时能够在attrs 参数中包含jobs 参数。这应该在:employees 字段中插入一个关联。
我可以在交互式 elixir shell 中执行此操作(来自 ElixirSchool 文档)
team = Venues.get_team!(1)
team_changeset = Ecto.Changeset.change(team)
team_add_employees_changeset = team_changeset |> put_assoc(:employees, [job])
Repo.update!(team_add_employees_changeset)
但我不确定如何将其抽象为 update_team 方法,该方法无需数据库调用即可构建变更集。
当我尝试这样做时:
%Team{}
|> Team.changeset(%{id: 1, name: "Floor"})
|> put_assoc(:employees, [job])
|> Repo.update()
我收到一条错误消息,抱怨 :employees 关联未加载:
** (Ecto.NoPrimaryKeyValueError) struct `%Runbook.Venues.Team{__meta__: #Ecto.Schema.Metadata<:built, "teams">, employees: #Ecto.Association.NotLoaded<association :employees is not
loaded>, id: nil, inserted_at: nil, name: nil, updated_at: nil, venue: #Ecto.Association.NotLoaded<association :venue is not loaded>, venue_id: nil}` is missing primary key value
(ecto) lib/ecto/repo/schema.ex:898: anonymous fn/3 in Ecto.Repo.Schema.add_pk_filter!/2
(elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
(ecto) lib/ecto/repo/schema.ex:312: Ecto.Repo.Schema.do_update/4
我可以这样做:
changeset = Team.changeset(%Team{}, %{id: 1, name: "Floor"})
team = Venues.get_team!(1) |> Repo.preload(:employees)
preloaded_changeset = %Ecto.Changeset{changeset | data: team}
preloaded_changeset |> put_assoc(:employees, [job]) |> Repo.update()
(未经测试,以下更简洁的版本)
%Ecto.Changeset{tc | data: Venues.get_team!(1) |> Repo.preload(:employees)} |> put_assoc(:employees, [job]) |> Repo.update()
最好/最干净/最传统的方法是什么?
【问题讨论】:
标签: database elixir associations ecto