Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[Rails]バリデーションのエラーメッセージを非同期通信で実装してみた!

Railsと日々格闘している「超」がつく初心者です。
自分自身の知識の整理のために、共有します。

はじめに

先日、Railsでの非同期通信の実装を学んだのですが、自分の中で消化し切れていない部分があったので、理解を深めるためにも、アウトプットします!
ユーザーオススメの本を投稿するサイトを想定しており、投稿された本に対して、コメントができます。
 ※コメント機能の非同期通信自体は、このページではあまり解説していません。

今回は、コメント欄を空欄で送信ボタンを押してしまった時に、エラーメッセージがコメント欄の真上に出るようにして、かつ、エラーメッセージ自体を非同期通信で実装してみよう、というものです。
それでは早速いきましょう!

バリデーション

models/comment.rb
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のコントローラー

comments_controller.rb
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のコントローラー

books_controller.rb
class BookController < ApplicationController
  before_action :authenticate_user!  #ログイン済ユーザーのみにアクセスを許可する

  def show
    @book = Book.find(params[:id])
    @comment = Comment.new  #Commentsコントローラーに値を渡す為のnewメソッド
  end
end

ここも特に問題ないでしょう!
折り返し地点に到達しました。
このペースで、Bookの詳細ページを確認していきましょう!

Booksのshowページ

app/views/books/show.html.erb
  <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をあえて記述する必要はありません。

エラーメッセージのパーシャル部分

app/views/layouts/_errors.html.erb
<% 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ファイル

app/views/comments/error.js.erb
$("#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ファイルも記載しておきます

app/views/comments/create.js.erb
$(".comments").html("<%= j(render 'comments/index', book: @book) %>");
$("#comment_comment").val("");

画面のイメージ図

非同期通信を実装したことにより、
image.png
このまま、空欄でコメントを送信しようとすると、
image.png
と、目論見通り怒られてしまいました。
ということはこれで、非同期通信を利用したエラーメッセージの完成!
メッセージ文を日本語に変換したりもできるのですが、誌面の都合上、またの機会に。

おわりに

非同期通信は、コントローラーとか、ページ間遷移とかが多くって、非常にこんがらがりますよね。
何度か読み返してもらえれば、理解に近づけるかと思います。
自分の中で、処理の流れを腹落ちさせれば、実装にかかる時間をどんどん短く出来るかもしれません。
拙文失礼いたしました。
一緒に勉強頑張りましょう!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away