我在评论模型中有以下内容(Phoenix 1.2):
schema "comments" do
belongs_to :parent, Test.Comment
belongs_to :user, Test.User
belongs_to :post, Test.Post
has_many :children, Test.Comment, foreign_key: :parent_id
end
在迁移过程中,我添加了:
create table(:comments) do
add :parent_id, :integer
add :user_id, references(:users, on_delete: :delete_all)
add :post_id, references(:posts, on_delete: :delete_all)
end
我想在帖子显示页面上显示博客文章,评论和评论的回复(嵌套评论,如reddit)。
评论和嵌套评论已正确创建,但我无法解析在帖子/展示页面上预先加载的“用户”显示嵌套评论。
所以在post_controller中,show函数我有这个:
post = Post
|> Repo.get(id)
|> Repo.preload(comments: from(c in Comment, order_by: [desc: c.inserted_at]),
comments: :user, comments: :parent, comments: :children)
在_comment.html.eex中,我放置的以下行会引发错误#Ecto.Association.NotLoaded<association :user is not loaded>
:
User: <%= @comment.user.username %>
任何有关此的帮助将不胜感激。
更新1:
输出post = Post |> Repo.get(48) |> Repo.preload(comments: from(c in Comment, order_by: [desc: c.votes_up]), comments: :user, comments: :parent, comments: :children)
作为带有评论和回复评论的示例帖子。
%Test.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">,
category: "new",
comments: [%Test.Comment{__meta__: #Ecto.Schema.Metadata<:loaded,
"comments">,
body: "hello there", children: [], id: 69,
inserted_at: ~N[2017-07-28 21:52:49.636919],
parent: nil, parent_id: nil,
post: #Ecto.Association.NotLoaded<association :post is not loaded>,
post_id: 48,
updated_at: ~N[2017-07-28 21:52:49.636933],
user: %Test.User{username: "dude",
comments: #Ecto.Association.NotLoaded<association :comments is not
loaded>,
...},
user_id: 11},
%Test.Comment{__meta__: #Ecto.Schema.Metadata<:loaded, "comments">,
body: "working there?",
children: [%Test.Comment{__meta__: #Ecto.Schema.Metadata<:loaded,
"comments">,
body: "real child reply should be seen in the show post page",
children: #Ecto.Association.NotLoaded<association :children is not
loaded>,
id: 85, inserted_at: ~N[2017-08-03 21:52:37.116894],
parent: #Ecto.Association.NotLoaded<association :parent is not
loaded>,
parent_id: 70,
post: #Ecto.Association.NotLoaded<association :post is not loaded>,
post_id: 48,
user: #Ecto.Association.NotLoaded<association :user is not loaded>,
user_id: 5}],
user: %Test.User{username: "dude", ...
更新2:
在帖子/节目模板中,我必须显示评论:
<%= for comment <- @post.comments do %>
<%= render "_comment.html", comment: comment, conn: @conn, post: @post %>
<% end %>
然后在_comment.html部分中我执行以下操作以显示父注释及其嵌套子注释:
<p>User: <%= @comment.user.username %></p>
<p>@comment.body</p>
<% unless Enum.empty?(@comment.children) do %>
<%= for child_comment <- @comment.children do %>
<ul class="nested_comment">
<%= render "_comment.html", comment: child_comment, conn: @conn, post: @post %>
</ul>
<% end %>
<% end %>
答案 0 :(得分:1)
来自docs
Repo.preload预加载给定结构或结构上的所有关联。
这类似于Ecto.Query.preload / 3,但它允许您在从数据库中提取结构后预加载结构。
如果已加载关联,则预加载不会尝试重新加载。
您更好的选择是使用Query.preload,因为您无论如何都在同一个管道中。
post = Repo.first(from p in Post,
join: c in assoc(p, :comments),
join: u in assoc(c, :user),
where p.id = ^id,
preload: [comments: {c, user: u}])
或者您可以通过选项:强制使用Repo.preload函数,但您还需要说明需要预先加载用户以进行评论关联。
修改强>
递归示例。
这是你的Post模块和结构
defmodule Post do
schema "posts" do
field :post_text, :string
has_many :comments, Comment, foreign_key: :post_id
end
@doc """
Recursively loads children into the given struct until it hits []
"""
def load_comments(model), do: load_comments(model, 10)
def load_comments(_, limit) when limit < 0, do: raise "Recursion limit reached"
def load_comments(%Post{comments: %Ecto.Association.NotLoaded{}} = model, limit) do
model
|> Repo.preload(:comments) # maybe include a custom query here to preserve some order
|> Map.update!(model, :comments, fn(list) ->
Enum.map(list, fn(c) -> c |> Comment.load_parents(limit - 1) |> Comment.load_children(limit-1) end)
end)
end
end
这是你的评论模块和结构。
defmodule Comment do
schema "comments" do
belongs_to :parent, Test.Comment
belongs_to :user, Test.User
belongs_to :post, Test.Post
has_many :children, Test.Comment, foreign_key: :parent_id
end
@doc """
Recursively loads parents into the given struct until it hits nil
"""
def load_parents(parent) do
load_parents(parent, 10)
end
def load_parents(_, limit) when limit < 0, do: raise "Recursion limit reached"
def load_parents(%Model{parent: nil} = parent, _), do: parent
def load_parents(%Model{parent: %Ecto.Association.NotLoaded{}} = parent, limit) do
parent = parent |> Repo.preload(:parent)
Map.update!(parent, :parent, &Model.load_parents(&1, limit - 1))
end
def load_parents(nil, _), do: nil
@doc """
Recursively loads children into the given struct until it hits []
"""
def load_children(model), do: load_children(model, 10)
def load_children(_, limit) when limit < 0, do: raise "Recursion limit reached"
def load_children(%Model{children: %Ecto.Association.NotLoaded{}} = model, limit) do
model = model |> Repo.preload(:children) # maybe include a custom query here to preserve some order
Map.update!(model, :children, fn(list) ->
Enum.map(list, &Model.load_children(&1, limit - 1))
end)
end
end
然后在控制器中
defmodule PostController do
def show(id) do
model = Repo.get(Post, id)
|> Post.load_comments
# rendering, etc...
end
end
答案 1 :(得分:0)
使用纯Ecto,我认为这是一个挑战,显示递归嵌套注释。您可以使用片段开发混合,如本答案https://stackoverflow.com/a/39400698/8508536
所示这是一个使用Elixir中的原始postgres的示例查询,简化了类似于我所做的事情:
qry = "
WITH RECURSIVE posts_r(id, posterid, parentid, parenttype, body, hash, depth) AS (
SELECT p.id, p.posterid, p.parentid, p.parenttype, body, age, hash, 1
FROM posts p
WHERE p.parentid = " <> post_id <> " AND p.parenttype != 'room'
UNION ALL
SELECT p.id, p.posterid, p.parentid, p.parenttype, p.body, p.age, p.hash, pr.depth + 1
FROM posts p, posts_r pr
WHERE p.parentid = pr.id AND p.parenttype != 'room'
)
SELECT psr.id, psr.parentid, psr.parenttype, psr.body, psr.hash, psr.depth, u.name
FROM posts_r psr LEFT JOIN users u ON psr.posterid = u.id
"
res = Ecto.Adapters.SQL.query!(Repo, qry, [])
cols = Enum.map res.columns, &(String.to_atom(&1))
comments = Enum.map res.rows, fn(row) ->
struct(Comment, Enum.zip(cols, row))
end
comments