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

Railsと日々格闘している「超」がつく初心者です。
自分自身の知識の整理のために、共有します。
#はじめに
先日、Railsでの非同期通信の実装を学んだのですが、自分の中で消化し切れていない部分があったので、理解を深めるためにも、アウトプットします！
ユーザーオススメの本を投稿するサイトを想定しており、投稿された本に対して、コメントができます。
　※コメント機能の非同期通信自体は、このページではあまり解説していません。

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

```ruby: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のコントローラー

```ruby: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のコントローラー

```ruby: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ページ

```ruby: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`をあえて記述する必要はありません。
#エラーメッセージのパーシャル部分

```ruby: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ファイル

```ruby: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ファイルも記載しておきます
```ruby:app/views/comments/create.js.erb
$(".comments").html("<%= j(render 'comments/index', book: @book) %>");
$("#comment_comment").val("");
```

#画面のイメージ図
非同期通信を実装したことにより、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/824345/39852f99-f7e5-b362-c6fa-eded235bbdd8.png)
このまま、`空欄`でコメントを送信しようとすると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/824345/a562fc7f-3a4d-a6f8-8dac-b889e28b0830.png)
と、目論見通り怒られてしまいました。
ということはこれで、非同期通信を利用したエラーメッセージの完成！
メッセージ文を日本語に変換したりもできるのですが、誌面の都合上、またの機会に。

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