#はじめに
コメント機能を非同期にしていく中で、投稿、削除は非同期にできたけど、バリデーションをかけた場合のエラーメッセージ、フラッシュメッセージの表示まで解説した記事がなかなか見つからなくて、時間がかかったので1つの記事にまとめて書いておきます。
初心者の方の参考になれば幸いです。
#この記事の対象となる人
- コメント投稿、削除機能を非同期にしたい人
- 他の記事を参考にして実装したが、エラーメッセージ、フラッシュメッセージが動かなくて困っている人
- 1つの記事だけを参考にしてコメント機能を非同期にしたい人
#開発環境
ruby 2.6.3
Rails 5.2.6
#前提
同期通信でのコメント機能は実装済みの状態から解説していきます。
挙動としては、下記を想定
コメントを投稿 → コメントが保存され、フラッシュメッセージが表示
コメントを削除 → コメントが削除され、フラッシュメッセージが表示
空でコメントを投稿 → バリデーションがかかって、エラーメッセージが表示
モデル名、変数名等は適宜、ご自身の開発環境に変換してお考えください。
user ユーザー
post 投稿
comment コメント
で進めていきます。
#現状のコードの確認
解説は省略しますが、現状のコードの確認をしていきます。
まずはモデルから
:
has_many :post, dependent: :destroy
has_many :comments, dependent: :destroy
:
:
belongs_to :user
has_many :comments, dependent: :destroy
:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
validates :comment, presence: true
end
次にコントローラー
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = current_user.comments.new(comment_params)
@comment.post_id = @post.id
if @comment.save
redirect_to post_path(@post), notice: 'コメントを投稿しました'
else
render 'posts/show'
end
end
def destroy
Comment.find_by(id: params[:id], post_id: params[:post_id]).destroy
redirect_to post_path(params[:post_id]), alert: 'コメントを削除しました'
end
private
def comment_params
params.require(:comment).permit(:comment)
end
end
:
def show
@post = Post.find(params[:id])
@comment = Comment.new
end
:
最後にビュー
<div class="post-body">
<%= attachment_image_tag @post, :image, :fill, 200, 200 %>
<p>説明:<%= @post.content %></p>
<p>ユーザーネーム:<%= @post.user.name %></p>
<p>投稿日時:<%= @post.created_at.strftime('%Y/%m/%d') %></p>
<% if @post.user == current_user %>
<%= link_to "削除", post_path(@post), method: :delete %>
<% end %>
<!--いいね機能-->
<div class="likes_buttons_<%= @post.id %>">
<%= render 'likes/like', post: @post %>
</div>
</div>
<!--コメントフォーム-->
<div class="new-comment">
<%= form_with(model:[@post, @comment], local: true) do |f| %>
<!--エラーメッセージ-->
<%= render 'layouts/error_messages', model: f.object %>
<%= f.text_area :comment, rows:'5',placeholder: "コメントをここに" %>
<%= f.submit "送信する" %>
<% end %>
</div>
<!--コメント一覧部分-->
<div class="comments">
<% if @post.comments.present? %>
<p>コメント件数:<%= @post.comments.count %></p>
<% @post.comments.each do |comment| %>
<%= comment.user.name %>
<%= comment.created_at.strftime('%Y/%m/%d') %><%= comment.comment %>
<% if comment.user == current_user %>
<div class="comment-delete">
<%= link_to "削除", post_comment_path(comment.post, comment), method: :delete, data: {confirm: "本当に削除しますか?"} %>
</div>
<% end %>
<% end %>
<% else %>
<p>コメントはまだありません</p>
<% end %>
</div>
<!--エラーメッセージ-->
<% if model.errors.any? %>
<div class="alert alert-danger">
<ul class="list-unstyled">
<h5><%= model.errors.count %>件のエラー</h5>
<% model.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
:
<main>
<!--フラッシュメッセージ-->
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
<%= yield %>
</main>
:
これが同期通信のコメント機能を実装済みのコードです。(レイアウトはかなり適当なので適宜、変更ください)
ではコメント機能を非同期にしていきましょう!
#ポイントの整理
ポイントは全部で4つ
- 部分テンプレートにする
- js対応にする
- 対応するjsファイルを作成
- コントローラーのリダイレクトを変更
上記ポイント押さえておきましょう!
#部分テンプレートにする
まずは、コメント機能のフォーム、コメント一覧の部分を部分テンプレートにして、divタグで囲います。
このdivタグで囲ったコメント一覧の部分を非同期で、更新していきます。
<%= form_with(model:[post, comment], local: true) do |f| %>
<!--エラーメッセージ-->
<%= render 'layouts/error_messages', model: f.object %>
<%= f.text_area :comment, rows:'5',placeholder: "コメントをここに" %>
<%= f.submit "送信する" %>
<% end %>
<% if post.comments.present? %>
<p>コメント件数:<%= post.comments.count %></p>
<% post.comments.each do |comment| %>
<%= comment.user.name %>
<%= comment.created_at.strftime('%Y/%m/%d') %><%= comment.comment %>
<% if comment.user == current_user %>
<div class="comment-delete">
<%= link_to "削除", post_comment_path(comment.post, comment), method: :delete, data: {confirm: "本当に削除しますか?"} %>
</div>
<% end %>
<% end %>
<% else %>
<p>コメントはまだありません</p>
<% end %>
:
<!--部分テンプレート化-->
<!--コメントフォーム-->
<div class="new-comment">
<%= render 'comments/form', post: @post, comment: @comment %>
</div>
<!--コメント一覧部分-->
<!--divタグのclassをid="comments_area"に変更-->
<div id="comments_area">
<%= render 'comments/post_comments', post: @post %>
</div>
:
#js対応にする
form_withとlink_toのデフォルトがhtml形式なので、jsファイルを探しに行ってくれるように、変更していきます。
form_withはlocal: trueをつけるとhtmlファイル
を、local: trueをつけないとjsファイルを探しに行ってくれる
ので、form_withのlocal: trueを外します。
<!--local: trueを削除-->
<%= form_with(model:[post, comment]) do |f| %>
<!--エラーメッセージ-->
<div id="comments-error">
<%= render 'layouts/error_messages', model: f.object %>
</div>
<%= f.text_area :comment, rows:'5',placeholder: "コメントをここに" %>
<%= f.submit "送信する" %>
<% end %>
削除ボタンのlink_toにremote: true
を追加
:
<!--remote: trueを追加-->
<%= link_to "削除", post_comment_path(comment.post, comment), method: :delete, remote: true, data: {confirm: "本当に削除しますか?"} %>
:
#対応するjsファイルをつくる
ここまでで、非同期で更新したい範囲、非同期で更新するためjsファイルを探しに行くようにすることができました。
まだ探しに行くjsファイルが無いので作成していきます。
views/comments直下
にpost_comments.js.erbを作成します。
<!--id="comments_areaにをrenderで指定した部分テンプレートに更新-->
$("#comments_area").html("<%= j(render 'comments/post_comments', post: @post, comment: @comment) %>");
<!--フォームを送信したあとにフォームの値を空にします-->
$("textarea").val('');
#コントローラーのリダイレクトを変更
最後にコントローラーのリダイレクトの部分をrenderでjsファイルを指定します。
def create
@post = Post.find(params[:post_id])
@comment = current_user.comments.new(comment_params)
@comment.post_id = @post.id
if @comment.save
flash.now[:notice] = 'コメントを投稿しました'
render :post_comments #render先にjsファイルを指定
else
render 'posts/show'
end
end
def destroy
Comment.find_by(id: params[:id], post_id: params[:post_id]).destroy
flash.now[:alert] = '投稿を削除しました'
#renderしたときに@postのデータがないので@postを定義
@post = Post.find(params[:post_id])
render :post_comments #render先にjsファイルを指定
end
これで、非同期でのコメントの投稿、削除ができました!
終了です。ありがとうございました。
ここまでの内容なら他の記事でもよくありましたが、肝心の空で送信したときのエラーメッセージ、投稿に成功したとき、削除したときのフラッシュメッセージが待てど暮らせど出てきません。。
ではさっそく非同期に対応させていきましょう!
#エラーメッセージの非同期化
エラーメッセージは投稿がバリデーションチェックに引っかかったとき(今回は空のコメント)に、表示させたいので、コメント機能を作ったときとは別のjsファイルを作って、コメントが保存されなかったときにだけ更新させるようにします。
手順は3つです。
- エラーメッセージの部分テンプレートをdivタグで囲う
- 囲ったdivタグを対象に非同期で更新させる
- コントローラーのrender先をjsファイルを指定
<%= form_with(model:[post, comment]) do |f| %>
<!--エラーメッセージをdivタグで囲む-->
<div id="comments-error">
<%= render 'layouts/error_messages', model: f.object %>
</div>
<%= f.text_area :comment, rows:'5',placeholder: "コメントをここに" %>
<%= f.submit "送信する" %>
<% end %>
次にviews/comments直下
にerror.js.erbを作成します。
$("#comments-error").html("<%= j(render 'layouts/error_messages', model: @comment) %>");
最後にコントローラーのrender先を変更します。
:
def create
@post = Post.find(params[:post_id])
@comment = current_user.comments.new(comment_params)
@comment.post_id = @post.id
if @comment.save
flash.now[:notice] = 'コメントを投稿しました'
render :post_comments
else
render :error #render先にjsファイルを指定
end
end
:
これでバリデーションに引っかかったときにエラーメッセージが非同期で実装できました。
#フラッシュメッセージの非同期化
最後にフラッシュメッセージも非同期で実装していきます。
フラッシュメッセージはコメントの投稿に成功したとき、削除したときに表示したいので、post_comments.js.erb
にフラッシュメッセージの記述を追加していきます。
手順は2つです。
- フラッシュメッセージを部分テンプレート化してdivタグで囲う
- 囲ったdivタグを対象に非同期で更新させる
:
<main>
<!--フラッシュメッセージの部分をdivタグで囲む-->
<div id="flash-message">
<%= render 'laouts/flash' %>
</div>
<%= yield %>
</main>
:
divタグで囲めたらid名を対象にjsファイルに追加します
$("#comments_area").html("<%= j(render 'comments/post_comments', post: @post, comment: @comment) %>");
$("textarea").val('');
<!--下記を追加-->
$('#flash-message').html("<%= j(render 'layouts/flash') %>");
これでコメントを投稿、削除したときにフラッシュメッセージが表示されるはずです。
ただ1つ注意点として、flash[:notice]のままだと投稿したり削除したりを何度もしたときに、前のフラッシュメッセージが残り続けてしまうので、flash.now
にしておいてください。
#最後に
自分が実装する際に、エラーメッセージ、フラッシュメッセージまで同じ記事で解説している記事が見つからず、かなり時間がかかってしまいましたので、今回書いて見ようと思いました。
エラーメッセージ、フラッシュメッセージが表示されなくて苦労したので、同じように苦労している方の参考になれたら幸いです。
かなり長くなってしまいましたが、最後まで見ていただきありがとうございます。