管理多对多关联
Manage many-to-many association
说,我有 Post 个属于许多标签的模型:
defmodule MyApp.Post do
use MyApp.Web, :model
schema "tours" do
field :title, :string
field :description, :string
has_many :tags, {"tags_posts", MyApp.Tag}
end
# …
end
保存 Post 时,我从多选字段中得到 tags_ids 列表:
tags_ids[]=1&tags_ids[]=2
问题是如何将 link 标签添加到 Post on save in Phoenix?
您要做的第一件事是修复模型。 Ecto 为多对多关系提供 has_many through:
语法。 Here are the docs.
多对多关系需要连接 table 因为标签和 post 都不能有直接指向彼此的外键(这会创建一对多关系).
Ecto 要求您在使用 has_many through:
.[=18= 的多对多关系之前使用 has_many
定义一对多连接 table 关系]
用你的例子,它看起来像:
defmodule MyApp.Post do
use MyApp.Web, :model
schema "posts" do
has_many :tag_posts, MyApp.TagPost
has_many :tags, through: [:tag_posts, :tags]
field :title, :string
field :description, :string
end
# …
end
这假设您有一个看起来像这样的联接 table tag_posts
:
defmodule MyApp.TagPost do
use MyApp.Web, :model
schema "tag_posts" do
belongs_to :tag, MyApp.Tag
belongs_to :post, MyApp.Post
# Any other fields to attach, like timestamps...
end
# …
end
如果您希望能够看到与给定标签关联的所有 post,请确保您在标签模型中以其他方式定义关系:
defmodule MyApp.Tag do
use MyApp.Web, :model
schema "posts" do
has_many :tag_posts, MyApp.TagPost
has_many :posts, through: [:tag_posts, :posts]
# other post fields
end
# …
end
然后,在您的控制器中,您想要使用您正在保存的 post 的 ID 和列表中的标签 ID 创建新的 tag_posts。
还不支持嵌套变更集:https://github.com/elixir-lang/ecto/issues/618您必须自己保存标签。
在下面的代码片段中,如果 Post.changeset/2
给我一个有效结果,我将选择 tag_ids
并将它们插入到联接 table 中。为了在表单中保留选定的标签,我添加了一个虚拟字段,我们可以在表单中读取它并设置默认值。这不是最好的解决方案,但对我有用。
后控制器
def create(conn, %{"post" => post_params}) do
post_changeset = Post.changeset(%Post{}, post_params)
if post_changeset.valid? do
post = Repo.insert!(post_changeset)
case Dict.fetch(post_params, "tag_ids") do
{:ok, tag_ids} ->
for tag_id <- tag_ids do
post_tag_changeset = PostTag.changeset(%PostTag{}, %{"tag_id" => tag_id, "post_id" => post.id})
Repo.insert(post_tag_changeset)
end
:error ->
# No tags selected
end
conn
|> put_flash(:info, "Success!")
|> redirect(to: post_path(conn, :new))
else
render(conn, "new.html", changeset: post_changeset)
end
end
PostModel
schema "posts" do
has_many :post_tags, Whosebug.PostTag
field :title, :string
field :tag_ids, {:array, :integer}, virtual: true
timestamps
end
@required_fields ["title"]
@optional_fields ["tag_ids"]
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
PostTagModel(用于创建多对多关联的 JoinTable)
schema "post_tags" do
belongs_to :post, Whosebug.Post
belongs_to :tag, Whosebug.Tag
timestamps
end
@required_fields ["post_id", "tag_id"]
@optional_fields []
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
邮政表格
<%= form_for @changeset, @action, fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= label f, :title, "Title" %>
<%= text_input f, :title, class: "form-control" %>
</div>
<div class="form-group">
<%= label f, :tag_ids, "Tags" %>
<!-- Tags in this case are static, load available tags from controller in your case -->
<%= multiple_select f, :tag_ids, ["Tag 1": 1, "Tag 2": 2], value: (if @changeset.params, do: @changeset.params["tag_ids"], else: @changeset.model.tag_ids) %>
</div>
<div class="form-group">
<%= submit "Save", class: "btn btn-primary" %>
</div>
<% end %>
如果你想更新标签,你有两个选择。
- 全部删除并插入新条目
- 查找更改,并保留现有条目
希望对您有所帮助。
说,我有 Post 个属于许多标签的模型:
defmodule MyApp.Post do
use MyApp.Web, :model
schema "tours" do
field :title, :string
field :description, :string
has_many :tags, {"tags_posts", MyApp.Tag}
end
# …
end
保存 Post 时,我从多选字段中得到 tags_ids 列表:
tags_ids[]=1&tags_ids[]=2
问题是如何将 link 标签添加到 Post on save in Phoenix?
您要做的第一件事是修复模型。 Ecto 为多对多关系提供 has_many through:
语法。 Here are the docs.
多对多关系需要连接 table 因为标签和 post 都不能有直接指向彼此的外键(这会创建一对多关系).
Ecto 要求您在使用 has_many through:
.[=18= 的多对多关系之前使用 has_many
定义一对多连接 table 关系]
用你的例子,它看起来像:
defmodule MyApp.Post do
use MyApp.Web, :model
schema "posts" do
has_many :tag_posts, MyApp.TagPost
has_many :tags, through: [:tag_posts, :tags]
field :title, :string
field :description, :string
end
# …
end
这假设您有一个看起来像这样的联接 table tag_posts
:
defmodule MyApp.TagPost do
use MyApp.Web, :model
schema "tag_posts" do
belongs_to :tag, MyApp.Tag
belongs_to :post, MyApp.Post
# Any other fields to attach, like timestamps...
end
# …
end
如果您希望能够看到与给定标签关联的所有 post,请确保您在标签模型中以其他方式定义关系:
defmodule MyApp.Tag do
use MyApp.Web, :model
schema "posts" do
has_many :tag_posts, MyApp.TagPost
has_many :posts, through: [:tag_posts, :posts]
# other post fields
end
# …
end
然后,在您的控制器中,您想要使用您正在保存的 post 的 ID 和列表中的标签 ID 创建新的 tag_posts。
还不支持嵌套变更集:https://github.com/elixir-lang/ecto/issues/618您必须自己保存标签。
在下面的代码片段中,如果 Post.changeset/2
给我一个有效结果,我将选择 tag_ids
并将它们插入到联接 table 中。为了在表单中保留选定的标签,我添加了一个虚拟字段,我们可以在表单中读取它并设置默认值。这不是最好的解决方案,但对我有用。
后控制器
def create(conn, %{"post" => post_params}) do
post_changeset = Post.changeset(%Post{}, post_params)
if post_changeset.valid? do
post = Repo.insert!(post_changeset)
case Dict.fetch(post_params, "tag_ids") do
{:ok, tag_ids} ->
for tag_id <- tag_ids do
post_tag_changeset = PostTag.changeset(%PostTag{}, %{"tag_id" => tag_id, "post_id" => post.id})
Repo.insert(post_tag_changeset)
end
:error ->
# No tags selected
end
conn
|> put_flash(:info, "Success!")
|> redirect(to: post_path(conn, :new))
else
render(conn, "new.html", changeset: post_changeset)
end
end
PostModel
schema "posts" do
has_many :post_tags, Whosebug.PostTag
field :title, :string
field :tag_ids, {:array, :integer}, virtual: true
timestamps
end
@required_fields ["title"]
@optional_fields ["tag_ids"]
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
PostTagModel(用于创建多对多关联的 JoinTable)
schema "post_tags" do
belongs_to :post, Whosebug.Post
belongs_to :tag, Whosebug.Tag
timestamps
end
@required_fields ["post_id", "tag_id"]
@optional_fields []
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
邮政表格
<%= form_for @changeset, @action, fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= label f, :title, "Title" %>
<%= text_input f, :title, class: "form-control" %>
</div>
<div class="form-group">
<%= label f, :tag_ids, "Tags" %>
<!-- Tags in this case are static, load available tags from controller in your case -->
<%= multiple_select f, :tag_ids, ["Tag 1": 1, "Tag 2": 2], value: (if @changeset.params, do: @changeset.params["tag_ids"], else: @changeset.model.tag_ids) %>
</div>
<div class="form-group">
<%= submit "Save", class: "btn btn-primary" %>
</div>
<% end %>
如果你想更新标签,你有两个选择。
- 全部删除并插入新条目
- 查找更改,并保留现有条目
希望对您有所帮助。