gem 'closure_tree' 实作嵌套评论
效果图

gem 'closure_tree'
class Event < ApplicationRecord has_many :comments, dependent: :destroy end class Comment < ApplicationRecord belongs_to :user belongs_to :event validates :body, presence: true has_closure_tree order: "created_at DESC", dependent: :destroy end class User < ApplicationRecord has_many :comments end
Rails.application.routes.draw do devise_for :users resources :events do resources :comments, except: [:show, :new, :index, :edit], controller: "event_comments" end root "events#index" end
<!-- Event Comment Section --> <%= render "event_comments/form" %> <!-- @event.comments index --> <%= comments_tree_for @comments %>
<div class="panel panel-comment"> <div class="panel-body"> <%= form_for @comment, url: event_comments_path(@event), method: :post do |f| %> <% from_reply_form ||= nil %> <% if from_reply_form %> <%= f.hidden_field :parent_id, value: parent.id %> <% else %> <%= f.hidden_field :parent_id, value: 0 %> <% end %> <div class="form-group"> <%= f.text_area :body, class: "form-control" %> </div> <%= f.submit "Submit", class: "btn btn-primary btn-xs" %> <% end %> </div> </div>
<div class="well well-sm well-comment"> <div class="tab-content"> <div id="comment-<%= comment.id %>" class="tab-pane fade in active"> <div class="media"> <div class="media-left"> <% if comment.user.logo.present? %> <%= image_tag(comment.user.logo.url(:thumb), class: "thumbnail thumbnail-sm") %> <% else %> <img src="<%= image_url 'default_user_logo.png' %>" alt="user logo" class: "thumbnail thumbnail-sm"> <% end %> </div> <div class="media-body"> <h4 class="media-heading"><%= comment.user.display_name %></h4> <p class="text-muted"><%= comment.created_at.to_s(:long) %></p> <p><%= simple_format comment.body %></p> </div> </div> </div> <div id="reply-<%= comment.id %>" class="tab-pane fade"> <div class="media"> <div class="media-left"> <% if comment.user.logo.present? %> <%= image_tag(comment.user.logo.url(:thumb), class: "thumbnail thumbnail-sm") %> <% else %> <img src="<%= image_url 'default_user_logo.png' %>" alt="user logo" class: "thumbnail thumbnail-sm"> <% end %> </div> <div class="media-body"> <h4 class="media-heading"> <%= comment.user.display_name %> </h4> <p class="text-muted"><%= comment.created_at.to_s(:long) %></p> <p><%= simple_format comment.body %></p> </div> </div> <!-- comment reply form --> <%= render "event_comments/form", from_reply_form: true, parent: comment %> </div> <div id="edit-<%= comment.id %>" class="tab-pane fade"> <!-- <%= render "event_comments/form", form_edit_form: true %> --> <%= form_for comment, url: event_comment_path(@event,comment), method: :put do |f| %> <div class="form-group"> <%= f.text_area :body, class: "form-control" %> </div> <%= f.submit "Submit", class: "btn btn-primary btn-xs" %> <% end %> </div> </div> <% if comment.leaf? %> <p><small class="text-muted text-success">还没有人回复--来互动一下!</small></p> <% end %> <ul class="nav nav-pills"> <li class="active" ><a data-toggle="pill" href="#comment-<%= comment.id %>">show</a></li> <li><a data-toggle="pill" href="#reply-<%= comment.id %>">reply</a></li> <% if current_user && comment.user == current_user %> <li><a data-toggle="pill" href="#edit-<%= comment.id %>">edit</a></li> <li><%= link_to "delete", event_comment_path(@event,comment), method: :delete %></li> <% end %> </ul> </div>
class EventsController < ApplicationController def show @event = Event.only_available.find_by_friendly_id!(params[:id]) @comment = Comment.new @comments = @event.comments.hash_tree end end
class EventCommentsController < ApplicationController before_action :authenticate_user! before_action :find_event def create if params[:comment][:parent_id].to_i > 0 parent = @event.comments.find_by_id(params[:comment].delete(:parent_id)) @comment = parent.children.build(comment_params) else @comment = @event.comments.new(comment_params) end @comment.user = current_user @comment.event = @event if @comment.save flash[:notice] = "comment 新建成功" end redirect_to event_path(@event) end def update @comment = @event.comments.find(params[:id]) if @comment.update(comment_params) flash[:notice] = "comment 修改成功" end redirect_to event_path(@event) end def destroy @comment = @event.comments.find(params[:id]) @comment.destroy flash[:alert] = "comment 已经删除" redirect_to event_path(@event) end private def find_event @event = Event.find_by_friendly_id(params[:event_id]) end def comment_params params.require(:comment).permit(:body) end end
// styles for comment button .nav.nav-pills { li > a { color: red; padding: 0.4px 10px; margin-right: 10px; &:hover { background-color: #FFFFFF; } } li.active > a { background-color: #FFFFFF; border: 1px solid red; &:focus, &:hover { color: red; border: 1px solid red; } } } .panel-comment, .well-comment { background-image: image-url("/images/white_wall.png"); } // comment layout .replies { margin-left: 50px; } .replies .replies .replies .replies .replies { margin-left: 0; }
几处重点
render @comments, render convention 约定,惯例;用reder 传递阐述
form_for url: new_event_comment_path(:parent_id), 重写 routing
f.hidden_field :parent_id, value: 0
params[:comment][:parent_id]
nil guard: 设定参数的默认值; from_reply_form ||= nil,
links:
https://www.sitepoint.com/nested-comments-rails/
https://github.com/ClosureTree/closure_tree#available-options