티스토리 뷰
Ruby on Rails : 중첩댓글 [Gem : act-as-commentable-with-threading] with Ajax 비동기 처리
나른한 하루 2019. 11. 5. 22:10해당 글은 https://blog.naver.com/kbs4674/221182443309 로 부터 게시글이 이전되었습니다.
댓글에 또 다른 답 댓글, 즉 중첩댓글에 대한 글을 다룹니다.
-
중첩댓글 : act as commentable with threading
참고
-
Post Scaffold에서 댓글을 구축하는 예시로 설명함.
-
Devise Gem을 기본적으로 사용한다는 가정 하에 사용하셔야 합니다. [참고]
1. Gemfile 에 다음 내용을 입력해줍니다.
gem 'acts_as_commentable_with_threading'
새로운 Gem을 설치해 주세요.
bundle install
2. 다음 명령어를 입력해서 Model과 DB(Migration 파일)를 생성해 줍니다.
rails generate acts_as_commentable_with_threading_migration
새로운 모델 파일과 테이블 파일이 생성되는걸 확인할 수 있습니다.
3. 새로 생성된 파일 중, 테이블 파일을 보면 Rails 버전이 없습니다.
class ActsAsCommentableWithThreadingMigration < ActiveRecord::Migration
제목 마지막에 자신의 레일즈에 맞는 버전으로 내용을 수정합니다.
저는 Rails 5.2 버전이라서 아래와 같이 수정했습니다.
class ActsAsCommentableWithThreadingMigration < ActiveRecord::Migration[5.2]
그리고 테이블 파일에서 아래와 같이 컬럼을 추가해주세요. (댓글 작성자 이메일 주소를 담을 컬럼)
class ActsAsCommentableWithThreadingMigration < ActiveRecord::Migration[5.2]
def self.up
create_table :comments, :force => true do |t|
t.string :nickname
...
end
def self.down
drop_table :comments
end
end
4. 게시판 역할을 맡을 Post scaffold를 생성합니다.
rails g scaffold Post title content:text user:references
5. DB를 Migrate 합니다.
rake db:migrate
6 댓글을 적용하려는 게시판 모델에 가셔서 다음 내용을 입력해주세요.
참고 Post Scaffold에 댓글을 적용하는 예시로 설명하겠습니다.
class Post < ApplicationRecord
...
belongs_to :user
acts_as_commentable
end
7. app/controllers 에서 comments_controller.rb 파일을 생성 후, 다음 내용을 입력해주세요!
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
commentable = commentable_type.constantize.find(commentable_id)
@comment = Comment.build_from(commentable, current_user.id, body)
@comment.nickname = params[:comment][:nickname]
respond_to do |format|
if @comment.save
make_child_comment
format.html { redirect_to(request.referrer, :notice => '댓글이 작성되었습니다.') }
else
format.html { redirect_to(request.referrer, :alert => '댓글 내용을 작성해주세요.') }
end
end
end
def destroy
@comment = Comment.find_by(id: params[:id])
@comment.delete
respond_to do |format|
format.html { redirect_to(request.referrer, :notice => '댓글이 삭제되었습니다.')}
format.js
end
end
private
def comment_params
params[:comment][:nickname] = current_user.email
params.require(:comment).permit(:body, :commentable_id, :commentable_type, :comment_id, :user_id, :nickname)
end
def commentable_type
comment_params[:commentable_type]
end
def commentable_id
comment_params[:commentable_id]
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
Attribute 명칭 참고
1) commentable_type
대댓글이 작성될 때 당시 게시글의 Model 이름에 대한 정보
2) commentable_id
게시글이 작성된 Model의 id값 (위 예제는 Post 모델의 3번 게시글에 작성된 댓글들임.)
3) parent_id
대댓글에서만 보이는 기록으로, 어떤 id의 댓글에서 대댓글이 달렸는지 알 수 있습니다.
8. app/controllers/posts_controller.rb 파일에서 show 액션 내부에 다음 내용을 입력해주세요!
def show
...
@new_comment = Comment.build_from(@post, current_user.id, "")
end
9. app/views/ 에 comments 라는 폴더를 생성해낸 뒤, app/views/comments 폴더 안에 다음 파일들을 생성 후 코드를 입력해주세요.
1) app/views/comments/_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 "댓글 작성", style: "float: right" %><br/>
</div>
<% end %>
2) app/views/comments/_reply.html.erb
<% comments.each do |comment| %>
<div class="comments-show" id="comment<%= comment.id %>">
<div class="comment">
<%= comment.user != nil ? comment.user.email : comment.email %> / <%= time_ago_in_words(comment.created_at) %>전 [ <%= comment.created_at.strftime('%Y-%m-%d %H:%M') %> ] /
<%= link_to "지우기", comment_path(comment), method: :delete, remote: false, data: { confirm: "정말로 지우시겠습니까?" }, style: "color: red; font-weight: bold" %>
<%= content_tag(:div, comment.body, style: "white-space: pre-wrap;") %>
<div class="comment-nav" align="right">
<a onclick="showHide('comment_reply_<%= comment.id %>')" onfocus="this.blur()">답댓글</a>
</div>
<div id="comment_reply_<%= comment.id %>" style="display:none;">
<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" style="margin-bottom: 60px">
<%= submit_tag "답댓글 작성", style: "float: right;" %>
</div>
<% end %>
</div>
</div>
</div>
<div style="margin-left: 100px;">
<%= render partial: "comments/reply_end", locals: {comments: comment.children} %>
</div>
</div>
<hr/>
<% end %>
3) app/views/comments/_template.html.erb
<div class="comments-header">
<h4>댓글 (<%= commentable.comment_threads.count %>)</h4>
</div>
<div class="comments-container">
<%= render partial: "comments/reply", locals: {comments: commentable.root_comments} %>
</div>
<%= render partial: "comments/form", locals: {new_comment: new_comment} %>
4) app/views/comments/_reply_end.html.erb
<% comments.each do |comment| %>
<div class="comments-show">
<div class="comment">
<%= comment.user != nil ? comment.user.email : comment.email %> / <%= time_ago_in_words(comment.created_at) %>전 [ <%= comment.created_at.strftime('%Y-%m-%d %H:%M') %> ] /
<%= link_to "지우기", comment_path(comment), method: :delete, remote: false, data: { confirm: "정말로 지우시겠습니까?" }, style: "color: red; font-weight: bold" %>
<%= content_tag(:div, comment.body, style: "white-space: pre-wrap;") %>
</div>
</div>
<% end %>
10. 각 파일별로 제시되는 내용을 원하는 위치에 입력해주세요!
1) app/views/posts/show.html.erb
<%= render partial: "comments/template", locals: {commentable: @post, new_comment: @comment} %>
참고 위 코드는 게시글에 작성된 댓글들을 보여줍니다.
2) app/views/posts/index.html.erb
<% @posts.each do |post| %>
...
<%= post.comment_threads.count %>
...
<% end %>
참고 위 코드는 댓글 갯수를 보여줍니다.
11. 라우터 규칙을 정의합니다.
##config/routes.rb
resources :comments, only: [:create, :destroy]
12. app/views/layouts/application.html.erb 에 다음 코드를 넣습니다. (답댓글 입력창을 을 접었다 폈다 할 수 있음.)
<script type="text/javascript">
function showHide(id) {
var obj = document.getElementById(id);
if (obj.style.display == 'none')
obj.style.display = 'block';
else
obj.style.display = 'none';
}
</script>
13. 끝입니다!
이후 디자인은 여러분들 몫입니다!
-
알아두면 좋은 지식 style: "white-space: pre-wrap;"
## 9번 과정의 4) 코드 내용 중
<%= content_tag(:div, comment.body, style: "white-space: pre-wrap;") %>
9번 과정에 입력한 코드 중, 작성된 댓글을 보여주는 위 코드가 있었습니다.
그리고 이 중, style: "white-space: pre-wrap;" 이 있는데 이 기능에 대해 짚고 넘어가보자 합니다.
우리가 글을 작성을 할 때 엔터를 쳐야하는 순간이 있습니다.
엔터를 치고 글을 작성 후, 결과물을 보면
보통은 HTML 태그에서는 엔터 처리를 뜻하는 <br/>태그 를 넣지 않는 한, 엔터처리를 자동으로 해주지 않습니다.
위 사례와 같이 엔터처리를 위해 존재하는 코드가 바로 style 속성에서 제공해주는 white-space: pre-wrap; 입니다.
style: "white-space: pre-wrap;" 을 입력 후, 결과를 보면
위와같이 결과가 잘 나오는게 확인됩니다.
그런데 어떤분은 이런 질문을 할 수 있습니다.
" Rails에서 제공하는 .html_safe 메소드를 쓰면 되지 않나요? "
.html_safe 메소드도 좋은 정답이긴 하나, 이미지 태그, audio 태그, meta 태그 등 불 필요한 태그의 사용도 허락을 하는 문제점이 있습니다.
반면에, style: "white-space: pre-wrap;" 는 위 사진과 같이 <br/> 태그만을 허용합니다.
-
참고 시간 오류가 뜰 경우
https://kbs4674.tistory.com/53#timeago 페이지의 Time-ago 과정을 따라하시면 됩니다.
-
알림잼 기능 연동하기
https://kbs4674.tistory.com/76 과정을 참고해주세요.
-
참고 Ajax 비동기 처리를 통한 댓글 추가 및 제거
비동기 처리를 이용한 댓글 처리
(제가 댓글을 쓴다해서 남들에게까지 실시간적으로 보여지는건 아닙니다.)
저희는 위 예시와 같이 비동기 처리를 통해 댓글이 새로고침 없이 작동되게 하고자 합니다!
1. app/views/comments 폴더에 create.js.erb 파일과 destroy.js.erb 파일을 생성합니다.
참고 create.js.erb 및 destroy.js.erb는 app/controllrs/comments_controller.rb 에서 따온 이름입니다.
다른 이름으로 쓰면 안됩니다.
2. 각 파일 별로 코드를 작성해주세요.
1) app/views/comments/create.js.erb
<% if @comment.parent == nil %> <%# 댓글에 대한 view 처리 %>
$("#comment-ajax").append("<%= escape_javascript render 'comments/comment_ajax', comment: @comment %>");
<% else %> <%# 대댓글에 대한 view 처리 %>
$("#reply-ajax<%= @comment.parent.id %>").append("<%= escape_javascript render 'comments/reply_ajax', comment: @comment, new_comment: @comment %>");
<% end %>
<%# 댓글 카운트가 동적으로 변화 %>
$('#comment_quantity').empty();
$('#comment_quantity').append('<%= @model_name.find("#{@model_id}").comment_threads.count %>');
2) app/views/comments/destroy.js.erb
$('#comment<%= @comment.id %>').remove(); <%# 삭제이벤트 발동 / 코멘트의 id값을 찾아서 댓글 제거 %>
<%# 댓글 카운트가 동적으로 변화 %>
$('#comment_quantity').empty();
$('#comment_quantity').append('<%= @model_name.find("#{@model_id}").comment_threads.count %>');
3. app/controllers/comments_controller.rb 파일을 열고 코드를 수정해주세요.
참고 + 코드 추가, - 코드 제거 혹은 주석처리 입니다.
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
commentable = commentable_type.constantize.find(commentable_id)
@comment = Comment.build_from(commentable, current_user.id, body)
@comment.nickname = params[:comment][:nickname]
# 댓글 비동기 처리 (모델 정보, 게시글의 ID 값을 찾아냄)
+ @model_name = eval(commentable_type)
+ @model_id = eval(commentable_id)
+ @new_comment = Comment.build_from(eval(commentable_type).find(commentable_id), current_user.id, "")
respond_to do |format|
if @comment.save
make_child_comment
+ format.js { flash[:notice] = "댓글이 작성되었습니다." }
- #format.html { redirect_to("#{request.referrer}#comment#{@comment.id}", :notice => '댓글이 작성되었습니다.') }
@comment.save
else
format.html { redirect_to(request.referrer, :alert => '댓글 내용을 작성해주세요.') }
end
end
end
def destroy
@comment = Comment.find_by(id: params[:id])
# 댓글 비동기 처리 (모델 정보, 게시글의 ID 값을 찾아냄)
+ @model_name = eval(@comment.commentable_type)
+ @model_id = @comment.commentable_id
@comment.destroy
respond_to do |format|
- # format.html { redirect_to(request.referrer, :notice => '댓글이 삭제되었습니다.') }
+ format.js { flash[:notice] = "댓글이 삭제되었습니다." }
end
end
...
end
4. app/views/comments/_template.html.erb 파일에서 코드를 수정해주세요.
참고 + 코드 추가, - 코드 제거 혹은 주석처리 입니다.
<div class="comments-header">
- <h4>댓글 (<%= commentable.comment_threads.count %>)</h4>
+ <h4>댓글 (<span id="comment_quantity"><%= commentable.comment_threads.count %></span>)</h4>
</div>
- <div class="comments-container">
+ <div class="comments-container" id="comment-ajax">
<%= render partial: "comments/reply", locals: {comments: commentable.root_comments} %>
</div>
<%= render partial: "comments/form", locals: {new_comment: new_comment} %>
5. app/views/comments/_form.html.erb 파일에서 코드를 수정해주세요.
참고 + 코드 추가, - 코드 제거 혹은 주석처리 입니다.
- <%= form_for @new_comment do |f| %>
+ <%= form_for @new_comment, remote: true, method: :post 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 "댓글 작성", style: "float: right" %><br/>
</div>
<% end %>
6. app/views/comments 폴더에 _comment_ajax.html.erb 및 _reply_ajax.html.erb 파일을 새로 생성해주세요.
7. 방금 과정에서 생성한 파일에 다음과 같이 입력해주세요.
1) app/views/comments/_comment_ajax.html.erb
<div class="comments-show" id="comment<%= comment.id %>">
<div class="comment">
<%= comment.user != nil ? comment.user.email : comment.email %> / <%= time_ago_in_words(comment.created_at) %>전 [ <%= comment.created_at.strftime('%Y-%m-%d %H:%M') %> ] /
<%= link_to "지우기", comment_path(comment), method: :delete, remote: true, data: { confirm: "정말로 지우시겠습니까?" }, style: "color: red; font-weight: bold" %>
<%= content_tag(:div, comment.body, style: "white-space: pre-wrap;") %>
<div class="comment-nav" align="right">
<a onclick="showHide('comment_reply_<%= comment.id %>')" onfocus="this.blur()">답댓글</a>
</div>
<div id="comment_reply_<%= comment.id %>" style="display:none;">
<div class="reply-form">
<%= form_for @new_comment, remote: true, method: :post 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" style="margin-bottom: 60px">
<%= submit_tag "답댓글 작성", style: "float: right;" %>
</div>
<% end %>
</div>
</div>
</div>
<div style="margin-left: 100px;" id="reply-ajax<%= comment.id %>">
</div>
<hr/>
</div>
2) app/views/comments/_reply_ajax.html.erb
<div class="comments-show" id="comment<%= comment.id %>">
<div class="comment">
<%= comment.user != nil ? comment.user.email : comment.email %> / <%= time_ago_in_words(comment.created_at) %>전 [ <%= comment.created_at.strftime('%Y-%m-%d %H:%M') %> ] /
<%= link_to "지우기", comment_path(comment), method: :delete, remote: true, data: { confirm: "정말로 지우시겠습니까?" }, style: "color: red; font-weight: bold" %>
<%= content_tag(:div, comment.body, style: "white-space: pre-wrap;") %>
</div>
</div>
8. 기존의 파일에서 일부 코드를 수정해주세요.
1) app/views/comments/_reply.html.erb
<% comments.each do |comment| %>
<%= render 'comments/comment_ajax', comment: comment %>
<% end %>
2) app/views/comments/_reply_end.html.erb
<% comments.each do |comment| %>
<%= render 'comments/reply_ajax', comment: comment %>
<% end %>
9. 이제 비동기 상태로 댓글이 실시간으로 올라가는것처럼 보여지는게 확인이 됩니다.
(하지만 실시간적으로 댓글이 달리는건 아니니 참고 바랍니다!!)
10. 읽을거리 Ajax 비동기 개념 이해 [클릭]
-
참고 자료
1. 이전페이지 이동 [클릭]
2. Gem 사용설명 참고 [클릭]
3. Gem 사용설명 참고(한국어, 간략설명) [클릭]
4. white-space: pre-wrap; 속성 [클릭]
'프로그래밍 공부 > Ruby on Rails : Gem' 카테고리의 다른 글
Ruby on Rails : 선입선출 Background Job [Gem : sidekiq] (0) | 2019.12.03 |
---|---|
Ruby on Rails : 스케쥴링(예약된 시간) Background Job [Gem : Whenever] (0) | 2019.12.03 |
Ruby on Rails : 띵동! 알람잼 [Gem : unread] (0) | 2019.11.05 |
Ruby on Rails : 투표 [Gem : acts_as_votable] (1) | 2019.11.05 |
Ruby on Rails : 어드민 관리자 페이지 [Gem : rails_admin] (0) | 2019.11.05 |