Railsと日々格闘している「超」がつく初心者です。
自分自身の知識の整理のために、共有します。
#はじめに
先日、Railsでの非同期通信の実装を学んだのですが、自分の中で消化し切れていない部分があったので、理解を深めるためにも、アウトプットします!
ユーザーオススメの本を投稿するサイトを想定しており、投稿された本に対して、コメントができます。
※コメント機能の非同期通信自体は、このページではあまり解説していません。
今回は、コメント欄を空欄で送信ボタンを押してしまった時に、エラーメッセージがコメント欄の真上に出るようにして、かつ、エラーメッセージ自体を非同期通信で実装してみよう、というものです。
それでは早速いきましょう!
#バリデーション
class BookComment < ApplicationRecord
belongs_to :user #Userモデルに紐付け
belongs_to :book #Bookモデルに紐付け
validates :body, presence: true, length: {maximum: 150}
end
コメントモデル内にて、presence: true
を設定したことにより、空欄での投稿を禁止しました。
ついでに、length: {maximum: 150}
も併せて設定しています。
さっそく、コントローラーを確認していきましょう!
#Commentsのコントローラー
class BookCommentsController < ApplicationController
before_action :authenticate_user! #ログイン済ユーザーのみにアクセスを許可する
def create
@book = Book.find(params[:book_id])
@comment = Comment.new(comment_params) #このタイミングでバリデーションチェックがかかる!
@comment.book_id = @book.id
@comment.user_id = current_user.id
unless @comment.save #@commentがsaveできなかった場合、
render 'error' #comments/error.js.erbを呼び出している(後述)
end
end
private
def comment_params
params.require(:comment).permit(:comment)
end
end
パラメーターを通ってきた値を使用して、newメソッドとsaveメソッドでデータベースに登録しています。
その際に、外部キーであるbook_idとuser_idはパラメーターでは入手ができないので、このタイミングで設定をしてあげています。
まあ、ここまでは大丈夫ですかね。
ここで大事なのは、バリデーションチェックはデータベースに保存される直前にされるので、エラー文で取得するインスタンス変数は
@comment = Comment.new(comment_params)
であるということです。
つまり、パラメータを通過してきた値それ自身に対して、エラーを含んでいるかどうかをチェックしているのです。
続いては、コメント欄はBookの詳細ページに入っているので、Booksのコントローラを確認しましょう!
#Booksのコントローラー
class BookController < ApplicationController
before_action :authenticate_user! #ログイン済ユーザーのみにアクセスを許可する
def show
@book = Book.find(params[:id])
@comment = Comment.new #Commentsコントローラーに値を渡す為のnewメソッド
end
end
ここも特に問題ないでしょう!
折り返し地点に到達しました。
このペースで、Bookの詳細ページを確認していきましょう!
#Booksのshowページ
<div id="comments_error">
<%= render 'layouts/errors', model: @comment %>
</div>
<%= form_with model:[@book,@comment] do |f| %>
<%= f.text_area :comment %>
<%= f.submit '送信'%>
<% end %>
前半のcomments_error
の部分に、layoutsの_errors.html.erb
をrenderで呼び出しています。
また、form_with
を利用して、コメント入力欄を設けているのですが、非同期通信を設定したいので、local:true
は記述せずにおきます。
尚、form_with
の場合はデフォルトで非同期通信になるので、remote: true
をあえて記述する必要はありません。
#エラーメッセージのパーシャル部分
<% if model.errors.any? %>
<div id="error_explanation">
<h3>
<%= pluralize(obj.errors.count, "error") %> prohibited this model from being saved:
</h3>
<ul>
<% model.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
Booksのshowページからrenderで呼び出していたのを覚えていますか。
いよいよ、エラーメッセージの登場です。
errors.full_messages
は全てのエラーメッセージを配列で取得しています。
複数のエラーに引っかかっていることも想定されるので、eachメソッドでループ処理をかけています。
このエラー文はデフォルトで入っているものなので、コメント欄以外にも様々な場面で利用できます。
#エラーメッセージの非同期通信で使用するjsファイル
$("#comments_error").html("<%= j(render 'layouts/errors', model: @comment) %>");
books/show.html.erb
の中で、id="comments_error"
の箇所を書き換える処理になります。
$("#comments_error")
でid = "comments_error"
をターゲットとし、render 'layouts/errors'
で指定しているlayouts/_errors.html.erb
の内容で書き換えています。
なお、ここでmodelに渡している@comment
ですが、これはどこで定義しているものか分かりますか?
jsファイルは、Commentsコントローラーで呼び出していましたね。
つまり、Commentsコントローラー内の、createアクションで定義している、
@comment = Comment.new(comment_params)
をlayouts/_errors.html.erb
に渡しているのです。
う〜ん、ややこしい!!
#(補足)コメント機能自体のjsファイルも記載しておきます
$(".comments").html("<%= j(render 'comments/index', book: @book) %>");
$("#comment_comment").val("");
#画面のイメージ図
非同期通信を実装したことにより、
このまま、空欄
でコメントを送信しようとすると、
と、目論見通り怒られてしまいました。
ということはこれで、非同期通信を利用したエラーメッセージの完成!
メッセージ文を日本語に変換したりもできるのですが、誌面の都合上、またの機会に。
#おわりに
非同期通信は、コントローラーとか、ページ間遷移とかが多くって、非常にこんがらがりますよね。
何度か読み返してもらえれば、理解に近づけるかと思います。
自分の中で、処理の流れを腹落ちさせれば、実装にかかる時間をどんどん短く出来るかもしれません。
拙文失礼いたしました。
一緒に勉強頑張りましょう!