実装すること
Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。
①コメントの作成を非同期で行う。
②コメントの件数もコメント作成と同時に非同期で更新されるようにする。
③3件目以上のコメントは隠して「もっと見る」を押すと見れるようにする。
④コメントの削除も非同期で行えるようにする。
完成形
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の中間テーブルになります。
ページ設計
投稿詳細ページ(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も消えるようにするためです。
class User < ApplicationRecord
has_many :items, dependent: :destroy
has_many :comments, dependent: :destroy
end
Itemモデル
class Item < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
end
Commentモデル
空欄で送信できないようバリデーションを掛けています。
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です。
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)
で、コメントを作成順に取ってきています。
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
と記載する必要があります。
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で書き換えます。
<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を渡しています。
<!-- コメント内容(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のインスタンスを渡すことが必要になります。
<!-- コメント入力フォーム ------------------------------------------------------------>
<%= 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('')
でコメント送信後のコメント入力フォームを空にしています。
$("#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
参考