acts_as_commentable_with_threadingを使ってみる

親子関係をもったコメント機能を簡単に実装できる、acts_as_commentable_with_threadingを使ってみたのでメモ。

こんな感じ。
f:id:hsaitou:20160223220326j:plain

GitHubのREADMEだけだと情報が少なかったんだけど、ここのまとめが素晴らしかった。
ただ、この記事のままだとエラーになるのでそこを修正、加えてコメントの削除がしたかったので追加してます。

ではでは、やってみましょう。

Gemfile.

gem 'acts_as_commentable_with_threading'

で、bundle installをする。

routes.rb

resources :comments

今回用のresourcesを追加する。

コメント管理用モデルを作成

コメントを保存しておくためのモデルを作成。

rails generate acts_as_commentable_with_threading_migration

で、rake db:migrateする。

そうすると、Commentsというテーブルが作成される。テーブルの中身はこんな感じ。

CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "commentable_id" integer, "commentable_type" varchar(255), "title" varchar(255), "body" text, "subject" varchar(255), "user_id" integer NOT NULL, "parent_id" integer, "lft" integer, "rgt" integer, "created_at" datetime, "updated_at" datetime);
CREATE INDEX "index_comments_on_commentable_id_and_commentable_type" ON "comments" ("commentable_id", "commentable_type");
CREATE INDEX "index_comments_on_user_id" ON "comments" ("user_id");

コメント機能を導入したいモデルを編集

次に、このコメント機能を取り入れたいモデルの編集。
今回はProfileというモデルに入れたかったので、app/model/profile.rbを編集。
acts_as_commentableを加えています。

class Profile < ActiveRecord::Base
  acts_as_commentable
end

コメントコントローラを作成

app/controllers/comments_controller.rbを作ります。
createの一部が参照サイトと一部異なっています。
そして、コメントを削除できるように、destroyを追加しています。

class CommentsController < ApplicationController
  def create
    commentable = commentable_type.constantize.find(params["comment"]["commentable_id"])
    @comment = Comment.build_from(commentable, current_user.id, body)

    respond_to do |format|
      if @comment.save
        make_child_comment
        format.html  { redirect_to(:back, :notice => 'Comment was successfully added.') }
      else
        format.html  { render :action => "new" }
      end
    end
  end

  def destroy
    @comment = Comment.find(params[:id])
    @comment.destroy
    respond_to do |format|
      format.html { redirect_to(:back, notice => 'コメントが削除されました。') }
    end
  end

  private

  def comment_params
    params.require(:comment).permit(:body, :commentable_id, :commentable_type, :comment_id)
  end

  def commentable_type
    comment_params[:commentable_type]
  end

  def comment_id
    comment_params[:comment_id]
  end

  def body
    comment_params[:body]
  end

  def make_child_comment
    return "" if comment_id.blank?

    parent_comment = Comment.find comment_id
    @comment.move_to_child_of(parent_comment)
  end

end

コメント機能を導入したいコントローラを編集

def show
  @profile = Profile.find(params[:id])
  @new_comment = Comment.build_from(@profile, current_user.id, "")
end

あとは、Viewファイルを作ります。

コメント機能を導入したいビューを編集

<%= render partial: "comments/template", locals: {commentable: @profile, new_comment: @comment} %>

app/view/comments配下のビューを作成

_template.html.erb

<div class="comments-header">
  <h4>
    <%= commentable.comment_threads.count == 0 ? "no" : commentable.comment_threads.count %> comments
  </h4>
</div>

<%= render partial: "comments/form", locals: {new_comment: new_comment} %>

<div class="comments-container">
  <%= render partial: "comments/reply", locals: {comments: commentable.root_comments} %>
</div>

_form.html.erb

<%= form_for @new_comment do |f| %>
  <%= f.hidden_field :commentable_id, value: @new_comment.commentable_id %>
  <%= f.hidden_field :commentable_type, value: @new_comment.commentable_type %>
  <div class="field form-group">
    <%= f.text_area :body, class: 'form-control' %>
  </div>
  <div class="field form-group">
    <%= submit_tag "Post comment", class: 'btn btn-primary' %>
  </div>
<% end %>

_reply.html.erb

<% comments.each do |comment| %>
  <div class="comments-show">
    <div class="comment">
      <p><%= simple_format(comment.body) %><br></p>
      <div class="comment-nav">
        <a href="#" class="comment-reply">返信する</a> | 
        <%= link_to '削除する', comment, method: :delete, data: { confirm: 'コメントを削除します。よろしいですか?' } %>
      </div>
      <div class="reply-form">
        <%= form_for @new_comment do |f| %>
          <%= f.hidden_field :commentable_id, value: @new_comment.commentable_id %>
          <%= f.hidden_field :commentable_type, value: @new_comment.commentable_type %>
          <%= f.hidden_field :comment_id, value: comment.id %>
          <div class="field form-group">
            <%= f.text_area :body, class: 'form-control' %>
          </div>
          <div class="field form-group">
            <%= submit_tag "Post Reply", class: 'btn btn-primary' %>
          </div>
        <% end %>
      </div>
    </div>
    <%= render partial: "comments/reply", locals: {comments: comment.children} %>
  </div>
<% end %>

CSS編集

最後にCSSを編集します。ここはまだいじる必要があると思う。

.comments-header {
 border-bottom: 1px dotted gray;
  margin-bottom: 10px;
}

.comments-container {
 p {
    margin-bottom: 0px;
  }
  .comment-nav {
    margin-left: 5px;
    margin-bottom: 10px;

    a {
      font-size: 10px;
     font-weight: bold;
      color: #888;
    }
  }
}

.reply-form {
  display: none;
}

.comments-show {
  margin-left: 25px;
}

.comment {
  background-color: #fff;
  padding: 8px;
  margin: 5px;
  border-radius: 10px;
}

以上です。便利だなぁ。