首先要知道的是没有一个“最佳实践”。
根据我的经验,有两种不同的方法:
cast_assoc
在你的User.changeset
函数中使用。
- 在您的
create_user
函数中,使用Ecto.Multi
创建:user
,然后使用此用户 ID 创建teacher
or student
。
这两个都不是“更好”。使用对您感觉更好的那个。
我现在将更详细地解释这两种方法:
1.cast_assoc
方法
在您的accounts.ex
上下文中,您将拥有:
def create_user(attrs) do
%User{}
|> User.creation_changeset(attrs)
|> Repo.insert()
end
地图将attrs
如下所示:
%{
password: "unhashed-password"
student: %{
first_name: "Bob",
last_name: "Smith"
}
}
或这个:
%{
password: "unhashed-password"
teacher: %{
first_name: "Bob",
last_name: "Smith"
}
}
我决定创建一个仅用于创建用户的变更集函数,因为有时我可能只想更新用户而不更新相关的教师/学生。所以,在user.ex
文件中,我有这个:
def creation_changeset(user, attrs) do
User
|> cast(attrs, @user_fields_to_cast)
|> cast_assoc(:teacher, with: &Teacher.user_creation_changeset/2)
|> cast_assoc(:student, with: &Student.user_creation_changeset/2)
end
差不多就是这样。
该create_user
函数应该返回一个{:ok, %User{}}
(或{:error, %Ecto.Changeset{}}
)元组,teacher
其中student
加载了用户(教师或学生应该是nil
)。
您不必使用专用的“创建”变更集,但我发现它通常会使这更容易。
2.Ecto.Multi
方法
如果您这样做,则无需使用cast_assoc
,您可以保持用户/教师/学生模式不变。相反,上下文函数看起来像这样:
alias Ecto.Multi
def create_user(attrs) do
{teacher_attrs, attrs} = Map.pop(attrs, :teacher)
{student_attrs, attrs} = Map.pop(attrs, :student)
Multi.new()
|> Multi.insert(:user, User.changeset(%User{}, attrs)
|> Multi.run(:teacher, fn repo, %{user: user} ->
if teacher_attrs do
%Teacher{}
|> Teacher.changeset(Map.put(teacher_attrs, :user_id, user.id))
|> repo.insert()
else
{:ok, nil}
end
end)
|> Multi.run(:student, fn repo, %{user: user} ->
if student_attrs do
%Student{}
|> Student.changeset(Map.put(student_attrs, :user_id, user.id))
|> repo.insert()
else
{:ok, nil}
end
end)
end
这将返回(在快乐的道路上),这个:
{:ok, %{user: user, student: student, teacher: teacher}}
学生或老师都将为零。
好了,我完成了。
我在浏览器中输入了所有这些内容,没有进行任何测试,所以要小心拼写错误、不匹配的括号和其他小问题!这只是说明性的。