首页 > 解决方案 > 嵌套评论线程 - 视图中的递归渲染

问题描述

我正在尝试在 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

标签: ruby-on-railsrubyrecursionviewcomments

解决方案


您在建模多态性方面有点失败。如果你想要一个真正的多态关联,你可以这样建模:

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

请注意可为空的外键列。与多态不同,这些是真正的外键,因此数据库将确保引用完整性。还要注意typeActiveRecord 中具有特殊意义的列。

然后让我们设置模型:

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 一样。


推荐阅读