Elixir Ecto 问题 - 无法在两个表之间建立关联?
Elixir Ecto Issue - Can't make an association between two tables?
我正在尝试在 table 之间建立关联。
我目前有一个 “用户” table,其中包含以下字段:
id
email
password
first_name
last_name
role
1
teacher@example.io
hash
Bob
White
teacher
2
student@example.io
hash
Joe
Brown
student
我正在尝试实现两种新的用户类型:教师和学生
因此,我想为两种用户类型创建两个不同的 tables,它们将引用主 table,如下所示:
- 教师table
id
user_id
first_name
last_name
1
1 (reference to "users")
Bob
White
- 学生table
id
user_id
first_name
last_name
1
2 (reference to "users")
Joe
Brown
我生成了一个新迁移并创建了“教师”table,如下所示:
create table("teachers") do
add :user_id, references("users")
add :first_name, :string
add :last_name, :string
end
然后我为教师 table 定义了一个模式,如下所示:
schema "teachers" do
field :first_name, :string
field :last_name, :string
belongs_to :user, User
最后,我将此添加到用户架构中:
has_many :teachers, Teacher
我现在已经成功地添加了教师 table 和我想要的列。问题是,我的注册表只包含一个变更集。用户模式进行所有验证并在注册成功时将用户添加到“用户”table。但是“老师”table 仍然是空的。用注册用户的信息填充教师 table 的最佳做法是什么?
首先要知道的是没有“最佳实践”。
根据我的经验,有两种不同的方法:
- 在你的
User.changeset
函数中使用 cast_assoc
。
- 在您的
create_user
函数中,使用 Ecto.Multi
创建 :user
,然后使用此用户 ID 创建 teacher
或 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"
}
}
我决定创建一个仅用于创建用户的变更集函数,因为有时我可能只想更新用户而不更新关联的 teacher/student。所以,在 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
函数应该return一个{:ok, %User{}}
(或{:error, %Ecto.Changeset{}}
)元组与teacher
和student
关联加载到用户(老师或学生应该是nil
)。
您不必使用专用的“创建”变更集,但我发现它通常会使这更容易。
2。 Ecto.Multi
方法
如果您这样做,则无需使用 cast_assoc
,您可以保留 User/Teacher/Student 模式。相反,上下文函数看起来像这样:
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
这将 return(在快乐的道路上),这:
{:ok, %{user: user, student: student, teacher: teacher}}
学生或老师都将为零。
好的,我说完了。
我在浏览器中输入了所有这些内容,但没有进行任何测试,因此请注意拼写错误、不匹配的括号和其他小问题!这只是说明性的。
我正在尝试在 table 之间建立关联。
我目前有一个 “用户” table,其中包含以下字段:
id | password | first_name | last_name | role | |
---|---|---|---|---|---|
1 | teacher@example.io | hash | Bob | White | teacher |
2 | student@example.io | hash | Joe | Brown | student |
我正在尝试实现两种新的用户类型:教师和学生
因此,我想为两种用户类型创建两个不同的 tables,它们将引用主 table,如下所示:
- 教师table
id | user_id | first_name | last_name |
---|---|---|---|
1 | 1 (reference to "users") | Bob | White |
- 学生table
id | user_id | first_name | last_name |
---|---|---|---|
1 | 2 (reference to "users") | Joe | Brown |
我生成了一个新迁移并创建了“教师”table,如下所示:
create table("teachers") do
add :user_id, references("users")
add :first_name, :string
add :last_name, :string
end
然后我为教师 table 定义了一个模式,如下所示:
schema "teachers" do
field :first_name, :string
field :last_name, :string
belongs_to :user, User
最后,我将此添加到用户架构中:
has_many :teachers, Teacher
我现在已经成功地添加了教师 table 和我想要的列。问题是,我的注册表只包含一个变更集。用户模式进行所有验证并在注册成功时将用户添加到“用户”table。但是“老师”table 仍然是空的。用注册用户的信息填充教师 table 的最佳做法是什么?
首先要知道的是没有“最佳实践”。
根据我的经验,有两种不同的方法:
- 在你的
User.changeset
函数中使用cast_assoc
。 - 在您的
create_user
函数中,使用Ecto.Multi
创建:user
,然后使用此用户 ID 创建teacher
或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"
}
}
我决定创建一个仅用于创建用户的变更集函数,因为有时我可能只想更新用户而不更新关联的 teacher/student。所以,在 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
函数应该return一个{:ok, %User{}}
(或{:error, %Ecto.Changeset{}}
)元组与teacher
和student
关联加载到用户(老师或学生应该是nil
)。
您不必使用专用的“创建”变更集,但我发现它通常会使这更容易。
2。 Ecto.Multi
方法
如果您这样做,则无需使用 cast_assoc
,您可以保留 User/Teacher/Student 模式。相反,上下文函数看起来像这样:
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
这将 return(在快乐的道路上),这:
{:ok, %{user: user, student: student, teacher: teacher}}
学生或老师都将为零。
好的,我说完了。
我在浏览器中输入了所有这些内容,但没有进行任何测试,因此请注意拼写错误、不匹配的括号和其他小问题!这只是说明性的。