说,我有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
保存帖子时,我从多选字段中获取tags_ids列表,如下所示:
tags_ids[]=1&tags_ids[]=2
问题是如何在凤凰城保存时将标签链接到帖子?
答案 0 :(得分:9)
在ecto中不支持嵌套的变更集:https://github.com/elixir-lang/ecto/issues/618您必须自己保存标记。
在以下代码段中,如果tag_ids
给我一个有效的结果,我会选择Post.changeset/2
并将它们插入到连接表中。为了保持表单I中的选定标签,我们添加了一个虚拟字段,我们可以在表单中读取并设置默认值。这不是最好的解决方案,但它对我有用。
<强> PostController中强>
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, Stackoverflow.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, Stackoverflow.Post
belongs_to :tag, Stackoverflow.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
<强> PostForm 强>
<%= 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 %>
如果您想更新标签,您有两种选择。
我希望它有所帮助。
答案 1 :(得分:8)
您要做的第一件事就是修复模型。 Ecto为多对多关系提供has_many through:
语法。 Here are the docs
多对多关系需要连接表,因为标签和帖子都不能将外键直接指向彼此(这将创建一对多关系)。
Ecto要求您在使用has_many
的多对多关系之前使用has_many through:
定义一对多联接表关系。
使用您的示例,它看起来像:
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
这假设您有一个类似于:
的联接表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
确保您是否希望能够查看与给定标记相关联的所有帖子,并在Tag模型中以另一种方式定义关系:
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
然后,在您的控制器中,您要创建新的tag_posts,其中包含您要保存的帖子的ID以及列表中标记的ID。