開発環境
Mac OS Catalina 10.15.7
ruby 2.6系
rails 6.0系
前提
同期でのコメント投稿機能は実装済みとする
JavaScriptのフレームワークは使っていません
各テーブルとアソシエーションは以下の通り
users テーブル
Column | Type | Options |
---|---|---|
nickname | string | null: false |
string | null: false, unique: true | |
encrypted_password | string | null: false |
Association
- has_many :posts
- has_many :comments
- has_many :likes
postsテーブル
Column | Type | Options |
---|---|---|
title | string | null: false |
explanation | text | |
category_id | integer | null: false |
animal_name | string | |
user | references | null: false, foreign_key: true |
Association
- belongs_to :user
- has_many :comments
commentsテーブル
Column | Type | Options |
---|---|---|
user | references | null: false, foreign_key: true |
post | references | null: false, foreign_key: true |
content | string | null: false |
Association
- belongs_to :user
- belongs_to :post
部分テンプレートに切り出す
まずは差し替えたい部分を切り出します。
自分の場合はコメント部分を切り出す事にしました。
切り出したものはapp/views/commentsのなかに配置しましょう。(commentsディレクトリが無ければ作ってください。)
# このIDは削除する時に使うもので、コメント投稿の非同期化には関係ありません。
<p id="comment_<%= comment.id %>">
<strong><%= link_to comment.user.nickname, user_path(comment.user.id) ,class: "comment-user"%>:</strong>
<%= comment.content %>
<% if user_signed_in? && current_user.id == comment.user.id %>
<span><%= link_to '[削除する]', post_comment_path(post.id, comment.id), method: :delete, class: "comment-delete", remote: true %></span>
<% end %>
</p>
<div class="comment-container">
<div class = "comment-box">
<h2>気になった投稿にコメントしよう!</h2>
<% if user_signed_in? %>
<%= form_with(model: [@post, @comment], remote: true) do |form| %>
<%= form.text_area :content, placeholder: "コメントする", rows: "2" %>
<%= form.submit "コメントを送信する" %>
<% end %>
<% else %>
<strong><p class = "alert">※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p>
</strong>
<% end %>
<div class="comments" id="comments">
<h4><コメント一覧></h4>
<% @comments.each do |comment| %>
# この部分を切り出しました。
<%= render "comments/comment", post: @post, comment: comment %>
<% end %>
</div>
</div>
</div>
ポイントは部分テンプレート内で使う変数を渡してあげる事です。
# この部分のこと
post: @post, comment: comment
form_withをremote: trueにする
次にform_withのlocal: trueをremote: trueに変更します。
変更前
<%= form_with(model: [@post, @comment], local: true) do |form| %>
<%= form.text_area :content, placeholder: "コメントする", rows: "2" %>
<%= form.submit "コメントを送信する" %>
<% end %>
変更後
<%= form_with(model: [@post, @comment], remote: true) do |form| %>
<%= form.text_area :content, placeholder: "コメントする", rows: "2" %>
<%= form.submit "コメントを送信する" %>
<% end %>
これで、コントローラーのcreateアクションのビューの参照先が、create.js.erbに変わりました。(コントローラーはlocal: trueにするとhtml.erbのビューを、remote: trueにするとjs.erbのビューを探しに行きます。)
コントローラーのリダイレクトの記述を削除する
せっかくremote: trueにしてもリダイレクトしてしまうので、削除しましょう。
変更前
class CommentsController < ApplicationController
def create
@comment = Comment.create(comment_params)
redirect_to post_path(params[:post_id])
end
def destroy
@comment = Comment.find(params[:id])
@comment.destroy
redirect_to post_path(params[:post_id])
end
private
def comment_params
params.require(:comment).permit(:content).merge(user_id: current_user.id, post_id: params[:post_id])
end
end
変更後
class CommentsController < ApplicationController
def create
@comment = Comment.create(comment_params)
end
def destroy
@comment = Comment.find(params[:id])
@comment.destroy
end
private
def comment_params
params.require(:comment).permit(:content).merge(user_id: current_user.id, post_id: params[:post_id])
end
end
create.js.erbを編集する
commentsディレクトリにcreate.js.erbファイルを作り、以下のように編集します。
var element = document.querySelector(".comments")
element.innerHTML += '<%= j(render partial: "comments/comment", locals: {post: @comment.post, comment: @comment}) %>'
document.querySelector("#comment_content").value = ""
コードを説明すると、まず1行目で、コメントの一覧表示がされるcommentsクラスを持つ要素を取得して、elementという変数に代入しています。(詳しくは上記のshow.html.erbを参照してください)
その後、変数elementにinnerHTMLを使い、切り出した_comment.html.erb(コメントの中身の部分)を追加しています。
また、_comment.html.erbでは変数commentと変数postが使われているので、commentsコントローラーで変数@commentに保存したコメントを代入する記述を書き(上記comments_controller.rb参照)、create.js.erbでlocalsオプションを使って、データを渡してあげましょう。
これでコメント投稿の非同期化は完成です。
しかし、このままだと、コメントの投稿フォームに、投稿したコメントが残ってしまうので、最後にコメントの中身を削除するために、投稿フォームをIDで取得して、空にする記述を書いています。
長くなりましたが、以上です。
参考になれば幸いです。