Elixir ecto 2 创建 many_to_many 关联
Posted
技术标签:
【中文标题】Elixir ecto 2 创建 many_to_many 关联【英文标题】:Elixir ecto 2 create many_to_many association 【发布时间】:2016-06-02 11:13:00 【问题描述】:如何与 ecto 2 建立多对多关系?作为一个示例应用程序,我想 创建一个可以属于多个类别的帖子。类别已经存在。例如:
[%Categoryid: "1", name: "elixir", %Categoryid: "2", name: "erlang"]
我正在使用 Ecto 2 beta 0。示例项目名为 Ecto2。
我定义了两个模型:
defmodule Ecto2.Post do
use Ecto2.Web, :model
use Ecto.Schema
schema "posts" do
field :title, :string
many_to_many :categories, Ecto2.Category, join_through: "posts_categories", on_replace: :delete
timestamps
end
@required_fields ~w(title)
@optional_fields ~w()
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> cast_assoc(:categories) # not suitable?
end
end
defmodule Ecto2.Category do
use Ecto2.Web, :model
schema "categories" do
field :name, :string
timestamps
end
@required_fields ~w(name)
@optional_fields ~w()
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
end
我试过这样做:
post = Repo.get!(Post, 1) |> Repo.preload(:categories)
changeset = Post.changeset(post, %"title"=> "bla", "categories"=> [%id: "1"])
Repo.update!(changeset)
但是 Post.changeset 中的 cast_assoc 不适合这个任务,它想创建一个全新的 Category 而不是关联一个。 我应该改用什么?建立关联?但是 build_assoc 文档没有提到它对 many_to_many 很有用。我该如何使用它?我应该将 build_assoc 放在 Post.changeset 中,还是应该在 phoenix 控制器中使用它。
【问题讨论】:
我可以看到下面的答案显示了一种通过使用 put_assoc(:categories, [category1]) (可能在控制器等中)在 Post 变更集之外解决此问题的方法。在 Post 变更集中解决它?就像你提到的, cast_assoc 创建新模型,它不与现有模型关联。 【参考方案1】:您可以通过传递诸如“posts_categories”之类的字符串来通过表加入,或者通过传递诸如 MyApp.PostCategory 之类的模式通过模式加入。我更喜欢通过模式加入,因为可以包含时间戳。假设您选择通过模式而不是表连接:
-
您需要为要加入的 many_to_many 关系创建一个单独的表(例如 :posts_categories)。
```
def change do
create table(:posts_categories) do
add :post_id, references(:posts)
add :category_id, references(:categories)
timestamps
end
end
-
为您在第 1 步中创建的表创建一个架构。在您的 web\models 文件夹中,创建一个文件 post_category.ex:
```
defmodule Ecto2.PostCategory do
use Ecto2.Web, :model
schema "posts_categories" do
belongs_to :post, Ecto2.Post
belongs_to :category, Ecto2.Category
timestamps
end
def changeset(model, params \\ %) do
model
|> cast(params, [])
end
end
Ecto beta 2 已将 :empty 更改为空地图并将 cast\4 更改为 cast \3。检查更新日志。
将此行添加到您的帖子架构中:
many_to_many :categories, Ecto2.Category, join_through: Ecto2.PostCategory
将此行添加到您的类别架构中:
many_to_many :posts, Ecto2.Post, join_through: Ecto2.PostCategory
就是这样!现在你可以像这样更新 ```
post1 = Repo.get!(Post, 1)
category1 = Repo.get!(Category, 1)
post1
|> Repo.preload(:categories)
|> Post.changeset(%)
|> put_assoc(:categories, [category1])
|> Repo.update!
```
【讨论】:
【参考方案2】:睡了一夜好觉并深入研究了 ecto 单元测试后,我找到了一个部分的答案。正确调用的函数是 Ecto.Changeset.put_assoc。它返回一个变更集。其余问题在此回复的底部。
def run_insert_1 do
c1 = Repo.get!(Category, 1)
c2 = %Categoryname: "cat 2"
# Inserting
changeset =
%Posttitle: "1"
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:categories, [c1, c2])
post = Repo.insert!(changeset)
IO.inspect post
end
def run_insert_2 do
c1 = Repo.insert! %Categoryname: "cat 1"
c2 = %Categoryname: "cat 2"
# Inserting
changeset =
%Posttitle: "1"
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:categories, [c1, c2])
post = Repo.insert!(changeset)
IO.inspect post
end
def run_update do
c1 = Repo.insert! %Categoryname: "cat update"
c2 = %Categoryname: "cat 2"
post = Repo.get!(Post, 1) |> Repo.preload(:categories)
# Updating
changeset =
post
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:categories, [c1])
post = Repo.update!(changeset)
IO.inspect post
end
这是一个部分解决方案,因为如果我想更新相关类别(帖子已经有相关类别的列表),我必须先删除然后保存空的类别列表。 可以一次性完成吗?
def run_update_2 do
c2 = Repo.get!(Tag, 2)
# Assumes Post 1 already has a few categories in it (for example after
# running run_update()
post = Repo.get!(Post, 1) |> Repo.preload(:categories)
# Remove and add again
changeset =
post
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:categories, [])
IO.inspect changeset
post = Repo.update!(changeset)
changeset =
post
|> Ecto.Changeset.change
|> Ecto.Changeset.put_assoc(:categories, [c2])
post = Repo.update!(changeset)
IO.inspect post
end
【讨论】:
以上是关于Elixir ecto 2 创建 many_to_many 关联的主要内容,如果未能解决你的问题,请参考以下文章