53
101

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Rails]Ajaxを用いて非同期でコメント機能の実装

Last updated at Posted at 2020-02-10

実装すること

Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。
①コメントの作成を非同期で行う。
②コメントの件数もコメント作成と同時に非同期で更新されるようにする。
③3件目以上のコメントは隠して「もっと見る」を押すと見れるようにする。
④コメントの削除も非同期で行えるようにする。

完成形

Image from Gyazo

Ajaxとは

✔︎同期通信

クライアントとサーバーが交互に処理を行い、同調して通信を行うこと。
→webブラウザがリクエストを送り、webサーバーが作成したHTMLファイルをレスポンスとして返し、webブラウザがそれを受け取って表示することでコンテンツの内容を変化させている。

・欠点
HTMLファイルを受け取ってから表示の処理を行うため、全体としてページの更新に時間がかかってしまう。
また、送信するデータも多くなりがちで、サーバーに負担がかかってしまう。

✔︎Ajax(非同期通信)

Webブラウザ上でJavascriptが直接Webサーバーと通信を行い、取得したデータを用いて表示するHTMLを更新する。データのやりとりにはXMLが用いられ、JavascriptはDOMを使ってXMLやHTMLを操作する。HTMLそのものをやりとりするのではなく、更新に必要なデータのみやりとりするため、送信するデータの量は同期通信の時よりも少なくなり、サーバーへの負担が抑えられる。

参考文献:「この1冊で全部わかる Web技術の基本」

ER図

User:Item = 1:N
Item:Comment = 1:N
User:Comment = 1:N
CommentテーブルがItemとUserの中間テーブルになります。
コメントER図.png

ページ設計

投稿詳細ページ(item/show.html.erb)でコメントができる。

モデルの作成

Userモデル、Itemモデルは作成した前提で進めていきます。
作成手順は下記リンク先で説明しております。
[非同期投稿と非同期いいねの実装]https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
それではCommentモデルを作成していきます。

$ rails g model Comment content:text user_id:integer item_id:integer
$ rails db:migrate

アソシエーションの確認

Userモデル

dependent: :destroyは、userが消えればitemもcommentも消えるようにするためです。

app/models/user.rb
class User < ApplicationRecord
 
 has_many :items, dependent: :destroy
 has_many :comments, dependent: :destroy

end

Itemモデル

app/models/item.rb
class Item < ApplicationRecord

  belongs_to :user
  has_many :comments, dependent: :destroy

end

Commentモデル

空欄で送信できないようバリデーションを掛けています。

app/models/comment.rb
class Comment < ApplicationRecord

  belongs_to :user
  belongs_to :item
  #バリデーション
  validates :content, presence: true

end

コントローラーの作成

$ rails g controller comments

ルーティングの作成

コメントがどの投稿へのものであるかを識別するために、ルーティングのURLに投稿のIDを含めます。(ネストする)
具体的には「/item/10/comment/」といったURLになります。10がitem_idです。

config/routes.rb
Rails.application.routes.draw do

  resources :users
  resources :items, only: [:index, :show, :new, :create] do
    resources :comments, only: [:create, :destroy]
  end

end

コントローラーの編集

items_controller.rb

order(created_at: :desc)で、コメントを作成順に取ってきています。

app/controllers/items_controller.rb
class ItemsController < ApplicationController
  
  def show
    @item = Item.find(params[:id])
    @comment = Comment.new
    #新着順で表示
    @comments = @item.comments.order(created_at: :desc)
  end

comments_controller.rb

buildを使うことで、@itemのidをitem_idに含んだ形でcommentインスタンスを作成します。
・保存がされると、render :indexによって「app/views/comments/index.js.erb」を探しにいきます。
form_withでフォームを送信した時は、デフォルトでjsファイルを探しにいく設定になっています。
htmlファイルを探しにいってほしい場合は、form_withの後にlocal: trueと記載する必要があります。
form_forでフォームを送信し、jsファイルを探しに行って欲しい場合はremote: trueと記載する必要があります。

app/controllers/comments_controller.rb

  class CommentsController < ApplicationController
	def create
      @item = Item.find(params[:item_id])
      #投稿に紐づいたコメントを作成
      @comment = @item.comments.build(comment_params)
      @comment.user_id = current_user.id
      @comment.save
      render :index
    end

    def destroy
      @comment = Comment.find(params[:id])
      @comment.destroy
      render :index
    end

    private
    def comment_params
      params.require(:comment).permit(:content, :item_id, :user_id)
    end
end

ビューの編集

items/show.html.erb

コメント一覧とコメント入力フォームはそれぞれパーシャルにしています。
id="comments_area" をターゲットにこのdiv内をAjaxで書き換えます。

app/views/items/show.html

<div class="row">
    <ul>
        <li class="comment-create">
            <h3 class="text-left title">レビュー</h3>
        </li>
  	    <li id="comments_area">
            <%= render partial: 'comments/index', locals: { comments: @comments } %>
	    </li>
    </ul>
    <hr>
    <% if user_signed_in? %>
        <div class="comment-create">
	        <h3 class="text-left">レビューを投稿する</h3>
	        <%= render partial: 'comments/form', locals: { comment: @comment, item: @item } %>
	    </div>
    <% end %>
</div>

comments/_index.html.erb

・each文のcomments.first(2)で最初の2件、comments.offset(2)で最初の2件以外を取ってきています。
・ 3件目以上は通常隠して、「もっと見る」を押すことで表示しています。これは Bootstrapのcollapseを使用しています。
参考: [Bootatrap4移行ガイド]https://cccabinet.jpn.org/bootstrap4/components/collapse
・コメントの削除は、remote:trueを付けることによって、コントローラのdestoryアクションから、index.js.erbを探しに行ってます。
また、(comment.item_id, comment.id)とすることで、投稿のidとコメントのidを渡しています。

app/views/comments/_index.html
<!-- コメント内容(2件) ------------------------------------------------------------------>
<%= comments.count %>件コメント
<h6 class="more" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">もっと見る....</h6>
<% comments.first(2).each do |comment| %>
  <% unless comment.id.nil? %>
  	<li class="comment-container">
		<div class="comment-box">
			<div class="comment-avatar">
				<%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", class:"comment-image", size: "40x40" %>
			</div>
			<div class="comment-text">
			    <p><%= link_to "@#{comment.user.name}", users_user_path(comment.user.id) %></p>
			    <div class="comment-entry">
  				    <%= comment.content %>
  				    <% if comment.user == current_user %>
			      		<%= link_to item_comment_path(comment.item_id, comment.id), method: :delete, remote: true, class: "comment_destroy" do %>
			      			<i class="fas fa-trash" style="color: black;"></i>
			      		<% end %>
			    	<% end %>
  				</div>
				<span class="comment-date pull-right">
				      <%= comment.created_at.strftime('%Y/%m/%d %H:%M:%S') %>
				</span>
			</div>
		</div>
	</li>
  <% end %>
<% end %>
<!-- コメント内容(3件目以降) ------------------------------------------------------------------>
	<div class="collapse" id="collapseExample">
			<% comments.offset(2).each do |comment| %>
			  <% unless comment.id.nil? %>
			  	<li class="comment-container">
					<div class="comment-box">
						<div class="comment-avatar">
							<%= attachment_image_tag comment.user, :profile_image, fallback: "no_image.jpg", class:"comment-image", size: "40x40" %>
						</div>
						<div class="comment-text">
						    <p><%= link_to "@#{comment.user.name}", users_user_path(comment.user.id) %></p>
						    <div class="comment-entry">
			  				    <%= comment.content %>
			  				    <% if comment.user == current_user %>
						      		<%= link_to item_comment_path(comment.item_id, comment.id), method: :delete, remote: true, class: "comment_destroy" do %>
			      						<i class="fas fa-trash" style="color: black;"></i>
			      					<% end %>
						    	<% end %>
			  				</div>
							<span class="comment-date pull-right">
							      <%= comment.created_at.strftime('%Y/%m/%d %H:%M:%S') %>
							</span>
						</div>
					</div>
				</li>
			  <% end %>
			<% end %>
	</div>

comments/_form.html.erb

form_withで、modle: [item, comment]としています。itemとcommentはそれぞれ、投稿のビューで渡しているインスタンス変数です。投稿に紐づいたコメントを生成するため、ここでitem,commentのインスタンスを渡すことが必要になります。

app/views/comments/_form.html

<!-- コメント入力フォーム ------------------------------------------------------------>
<%= form_with(model: [item, comment], url: item_comments_path(@item) ) do |f| %>
    <%= f.text_area :content, class: "input-mysize" %>
    <%= f.submit "送信", class: "btn btn-outline-dark comment-submit float-right" %>
<% end %>

comments/index.js.erb

・items/show.html.erbの中でid="comments_area"の箇所を書き換える処理になります。
$("#comments_area")id = "comments_area"をターゲットとし、render 'index'で指定しているcomments/_index.html.erbの内容で書き換えています。
$("textarea").val('')でコメント送信後のコメント入力フォームを空にしています。

app/views/comments/index.js
$("#comments_area").html("<%= j(render 'index', { comments: @comment.item.comments }) %>")
$("textarea").val('')

最後に

最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
Ajax関連でいいねとフォローの非同期も実装しています。

[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装
https://qiita.com/yuto_1014/items/78d8b52d33a12ec33448
[Rails]Ajaxを用いて非同期でフォロー機能の実装
https://qiita.com/yuto_1014/items/8d508b84fd0c2316ba01

参考

53
101
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
101

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?