ruby-on-rails - 嵌套评论线程 - 视图中的递归渲染
问题描述
我正在尝试在 Rails 6 中创建一个问答线程,用户可以在其中回答问题,然后其他用户可以对答案发表评论——类似于 reddit 甚至 stackoverflow。
我在我的 Answer 模型上创建了一个带有“parent_id”的多态关联,并且我能够在最初的问题上发布答案。但是,嵌套答案不会呈现在初始答案之下,而是呈现在主要问题之下。我想我已经将问题隔离到下面看到的相应部分视图中:
答案视图
<li>
<%= answer.body %></br>
<%= link_to answer.user.first_name, answer.user %>
<%= link_to answer.user.last_name, answer.user %>
answered <%= time_ago_in_words(answer.created_at) %> ago.
<div class="comments-container">
<%= render partial: "answers/reply", locals: {commentable: answer.commentable, parent_id: answer.parent.id} %>
</div>
<ul> <%= render partial: "answers/answer", collection: answer.answers %> </ul>
</li>
据我了解,最后一行应该呈现答案的答案,但是答案呈现在最初的问题下方,而不是答案。关于我做错了什么的任何想法?我应该使用像Ancestry这样的宝石来做到这一点吗?如果是这样,那将如何工作?
为了完整起见,这里是其他组件
问题视图
<h3><%= @question.title %></h3>
<p> Asked by <%= link_to @question.user.email, @question.user %> <%= time_ago_in_words(@question.created_at) %> ago. </p>
</br>
<span class="body"> <%= @question.body %> </span>
</br>
<h5><strong><%= @question.answers.count %> Answers</strong></h5>
<%= render @answers %></br>
<%= render partial: "answers/form", locals: {commentable: @question} %> </br>
<%= paginate @answers %>
答案模型
belongs_to :user
belongs_to :parent, optional: true, class_name: 'Answer'
belongs_to :commentable, polymorphic: true
has_many :answers, as: :commentable, dependent: :destroy
validates :body, presence: true
validates :user, presence: true
问题模型
belongs_to :user
has_many :answers, as: :commentable, dependent: :destroy
validates :body, presence: true
validates :title, presence: true
validates :user, presence: true
应答控制器
class AnswersController < ApplicationController
before_action :set_answer, only: [:edit, :update, :destroy, :upvote, :downvote]
before_action :find_commentable, only: [:create]
def new
@answer = Answer.new
end
def create
@answer = @commentable.answers.new(answer_params)
respond_to do |format|
if @answer.save
format.html { redirect_to @commentable }
format.json { render :show, status: :created, location: @commentable }
else
format.html { render :new }
format.json { render json: @answer.errors, status: :unprocessable_entity }
end
end
end
def destroy
@answer = @commentable.answers.find(params[:id])
@answer.discard
respond_to do |format|
format.html { redirect_to @commentable, notice: 'Answer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_answer
@answer = Answer.find(params[:id])
end
def answer_params
params.require(:answer).permit(:body).merge(user_id: current_user.id, parent_id: params[:parent_id])
end
def find_commentable
@commentable = Answer.find(params[:answer_id]) if params[:answer_id]
@commentable = Question.find(params[:question_id]) if params[:question_id]
end
end
问题控制器
class QuestionsController < ApplicationController
before_action :set_question, only: [:show, :edit, :update, :destroy, :upvote, :downvote]
def index
@questions = Question.order('created_at desc').page(params[:page])
end
def show
@answer = @question.answers.new
@answers = if params[:answer]
@question.answers.where(id: params[:answer])
else
@question.answers.where(parent_id: nil)
end
@answers = @answers.page(params[:page]).per(5)
end
def new
@question = Question.new
end
def edit
end
def create
@question = Question.new(question_params)
respond_to do |format|
if @question.save
format.html { redirect_to @question, notice: 'You have successfully asked a question!' }
format.json { render :show, status: :created, location: @question }
else
format.html { render :new }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if @question.update(question_params)
format.html { redirect_to @question, notice: 'Question successfully updated.' }
format.json { render :show, status: :ok, location: @question }
else
format.html { render :edit }
format.json { render json: @question.errors, status: :unprocessable_entity }
end
end
end
def destroy
@question.discard
respond_to do |format|
format.html { redirect_to @questions_url, notice: 'Question successfully deleted.' }
format.json { head :no_content }
end
end
private
def set_question
@question = Question.find(params[:id])
end
def question_params
params.require(:question).permit(:title, :body, :tag_list).merge(user_id: current_user.id)
end
end
解决方案
您在建模多态性方面有点失败。如果你想要一个真正的多态关联,你可以这样建模:
class Question
has_many :answers, as: :answerable
end
class Answer
belongs_to :answerable, polymorphic: true
has_many :answers, as: :answerable
end
这让问题的“父母”既可以是问题也可以是答案,您不需要做一些荒谬的事情,例如@question.answers.where(parent_id: nil)
. 你可以这样做@answers = @question.answers
,这将只包括第一代孩子。
然而,多态性并不是它的全部内容,这在构建树层次结构时尤其明显。由于我们实际上必须将行从数据库中提取出来才能知道在哪里加入,所以您不能急切地有效地加载树。如果父类的数量很大或未知,或者您只是原型设计,多态性主要有用。
相反,您可以使用单表继承来设置关联:
class CreateAnswers < ActiveRecord::Migration[6.0]
def change
create_table :answers do |t|
t.string :type
t.belongs_to :question, null: true, foreign_key: true
t.belongs_to :answer, null: true, foreign_key: true
# ... more columns
t.timestamps
end
end
end
请注意可为空的外键列。与多态不同,这些是真正的外键,因此数据库将确保引用完整性。还要注意type
ActiveRecord 中具有特殊意义的列。
然后让我们设置模型:
class Question < ApplicationRecord
has_many :answers, class_name: 'Questions::Answer'
end
class Answer < ApplicationRecord
has_many :answers, class_name: 'Answers::Answer'
end
以及 Answer 的子类:
# app/models/answers/answer.rb
module Answer
class Answer < ::Answer
belongs_to :answer
has_one :question, through: :answer
end
end
# app/models/questions/answer.rb
module Questions
class Answer < ::Answer
belongs_to :question
end
end
很酷。现在我们可以通过以下方式预先加载到第一代和第二代:
Question.eager_load(answers: :anser)
我们可以继续:
Question.eager_load(answers: { answers: :answer })
Question.eager_load(answers: { answers: { answers: :answers }})
但是在某些时候,您会想要退出并开始使用 ajax,就像 reddit 一样。
推荐阅读
- performance - 计算加速
- angular - 如何以角度显示过滤的对象列表?
- python - 如何识别字符串的最小形式?
- unity3d - 无法使用“屏幕空间叠加”在画布中获取对象的真实位置
- angular - ASP.Net Core 服务器中的 Angular PWA - Serviceworker-Cache 不起作用
- verilog - verilog/系统verilog中的最大线位宽度是多少
- python - 有哪些方法可以为 Flask 应用程序播种?
- python - 如何删除换行符但在文本文件中保留空白行?
- mysql - 将动态参数传递给 gman_do mysql udf 函数
- apache-nifi - 通过 SplitJson 和 MergeContent 过滤 JSON