Railsの基礎を使って、メッセージ投稿アプリを作ってみた　【第３章】（6.エラーの処理）

## 概要
前回の記事（第２章）の続きです。
[Railsの基礎を使って、メッセージ投稿アプリを作ってみた　【第２章】（3.リファクタリング〜5.バリデーションの設定まで](https://qiita.com/web_fumiya/items/c58bbae366d4d813296f)

今回の第３章では、バリデーションエラーが発生した際のエラーの処理を実装していきます。

## 開発環境
- Ruby 2.7.3
- Rails 6.1.6.1
- Postgresql

## 開発手順
1. アプリの作成 (第１章)
2. CRUD処理で簡単なメッセージ投稿アプリを実装 (第１章)
3. リファクタリング （第２章）
4. フラッシュの実装 （第２章）
5. バリデーションの設定 （第２章）
6. エラーの処理 <font color="DeepPink">（第３章）</font>
7. Bootstrapでスタイルをつける （第4章）

## エラーの処理
前回の記事でバリデーションを設定したことにより、空文字投稿、文字数制限を超える投稿をしたら<font color="DeepPink">バリデーションエラー</font>が発生するようになりました。
そのエラーをユーザーにわかりやすく伝わるようにエラーメッセージを表示する作業に入ります。

### エラーメッセージの日本語化
まずはGemfileに<font color="DeepPink"> rails-i18n </font>を追加し、多言語に対応できるようにします。
```:Gemfile
# 略

gem 'bootsnap', '>= 1.4.4', require: false
gem 'rails-i18n', '~> 6.0.0'

group :development, :test do

# 略
```
```terminal:ターミナル
bundle install
```
多言語に対応できるようになったので、次に日本語に設定します。
<font color="DeepPink">config/application.rb </font>に`config.i18n.default_locale = :ja`を追記。
```ruby:config/application.rb
# 略
class Application < Rails::Application
    
    config.load_defaults 6.1
    config.i18n.default_locale = :ja
# 略
```
次に日本語訳をダウンロード
今回は、直接<font color="DeepPink"> curl </font>コマンドでアプリ内に入れてしまします。
```terminal:ターミナル
curl -o config/locales/ja.yml -L https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml
```
これでエラーメッセージが日本語化されましたが、モデルのカラムは日本語化されていないので、`config/locales/ja.yml`に追記が必要です。
(yml形式は、<font color="DeepPink">インデント</font>が１マスでもずれるとエラーになるので注意しましょう。)
```yml:config/locales/ja.yml
---
ja:
  activerecord:
    models:
      post: メッセージ
    attributes:
      post:
        title: タイトル
        content: 内容
    errors:
# 略
```
ついでに、上記でカラムの日本語を設定したことで、投稿、更新フォームのラベルとしても使用できるので、変更しておきます。
```erb:app/views/posts/_form.html.erb
<%= form_with model: @post, local: true do |form| %>
  <div>
    <%= form.label :title %> <!-- タイトルを消去　-->
    <%= form.text_field :title %>
  </div>
  <div>
    <%= form.label :content %> <!-- 内容を消去 -->
    <%= form.text_field :content %>
  </div>
```
これで、エラーメッセージを日本語化できたので、コンソールで確認してみましょう。
```console:コンソール
Post.create!(title: "", content: "")
Post.create!(title: "テスト", content: "")
Post.create!(title: "", content: "テスト")
Post.create!(title: "1" * 51, content: "テスト")
Post.create!(title: "テスト", content: "1" * 501)
```
エラーメッセージが日本語で表示されればOKです。

### バリデーションエラー時の処理を変更
現状バリデーションエラーが発生すると、エラー画面が表示されます。
エラー画面ではなく、エラーメッセージが表示されるように、まずはコントローラでエラー時の処理を変更する必要があります。

<font color="DeepPink">if 文 </font>を使って投稿内容にエラーがなければ、通常通りデータベースに保存し、指定のページにリダイレクト。
エラーがあった場合は、フラッシュメッセージを設定し、投稿ページ、もしくは更新ページにレンダリングする形とします。
```rb:app/controllers/posts_controller.rb
# 略
  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to post_path(post), notice: '投稿しました'
    else
      flash.now[:alert] = '投稿に失敗しました'
      render :new
    end
  end

# 略

  def update
    if @post.update(post_params)
      redirect_to root_path, notice: '更新しました'
    else
      flash.now[:alert] = '更新に失敗しました'
      render :edit
    end
  end
# 略
```
`create`アクション、`update`アクションでは、`@post = Post.create!`のように`!`をつけて例外が発生するようにしていました。
（例外とは、エラー画面が表示されることを意味します）
それを、`!`をつけないように変更。

次に保存に失敗した場合は、フラッシュメッセージを設定し、<font color="DeepPink">リダイレクト</font>ではなく、<font color="DeepPink">レンダリング</font>するように実装。
理由としては、リダイレクトにすると、エラーが発生したときに入力した内容が消えます。
レンダリングにすることで、入力した内容が残ったまま投稿ページ、もしくは更新ページに移動することができます。

フラッシュメッセージの設定ではレンダリングとフラッシュを併用するので、<font color="DeepPink">flash.now</font>を使用。
<font color="DeepPink">flash.now</font>であれば、次のアクションが実行された時点で、メッセージが消えるからです。



ここでレンダリングするときに注意点があります。
バリデーションエラーが発生した場合、
<font color="DeepPink">エラー箇所のラベル、フィールドが自動的に改行されてしまう。</font>

これはRailsの仕様で、エラー箇所に<font color="DeepPink">div</font>タグがつく<font color="DeepPink">field_with_errors</font>クラスが作成されるからです。
この仕様を使って、エラー箇所のフィールドだけ色を変え、目立たせることなどが可能となります。

なので、<font color="DeepPink">div</font>タグで改行されてしまったのを、修正し、エラー箇所のフィールドを赤色になるよう修正していきます。
```scss:app/assets/stylesheets/application.scss
/* 略 */

div.field_with_errors {
  display: contents;

  input {
    border: 2px solid red;
  }
}
```
今回、フィールドを赤色にする作業で、SCSS記法を用いているので、拡張子を　<font color="DeepPink">CSS</font>から<font color="DeepPink">SCSS</font>に変更しています。
これで、改行の修正、エラー時のフィールド枠の色変更は完了です。

### エラーメッセージの表示
現状、バリデーションエラーが発生したら、
- 投稿ページにレンダリングされ、フラッシュメッセージが表示される。
- エラー箇所のフィールドが赤色になる。

しかし肝心のエラーメッセージが表示されていないので、何がエラーの原因なのかわからない状態です。

なので、これからエラーメッセージをビューファイルに表示する作業に入ります。
まずコンソールで、エラー時の状況を確認してみます。
```console:コンソール
post = Post.new(title: "テスト", content: "1" * 51)
post.save
```
`post.save`を実行したら、`false`と返ってきます。
```console:コンソール
post.errors
post.errors.full_messages
```
`save`メソッド実行後に、`post.errors`を実行すると、
```console:コンソール
@errors=[#<ActiveModel::Error attribute=content, type=too_long, options={:count=>500}>]>
```
<font color="DeepPink">content</font>が長すぎる(500文字を超えている)と書いてあります。
`post.errors.full_messages`を実行すると、
```console:コンソール
=> ["内容は500文字以内で入力してください"]
```
とエラー内容が表示されました。
これをビューファイルに表示させれば、エラーの原因が分かります。
エラーメッセージは、フォームの上に表示させる方がわかりやすいので、別でエラーメッセージのファイルを作成し、レンダリングする形とします。

【新規作成】
```erb:app/views/layouts/_error_messages.html.erb
<% if @post.errors.any? %>
  <div>
    <ul>
      <% @post.errors.full_messages.each do |msg| %>
        <li><span style="color: red;"><%= msg %></span></li>
      <% end %>
    </ul>
  </div>
<% end %>
```
`_form.html.erb`の一番上に書き、レンダリング。
```erb:app/views/posts/_form.html.erb
<%= render "layouts/error_messages" %>
<%= form_with model: @post, local: true do |form| %>
  <div>
<!-- 略 -->
```

サーバーを起動し、空投稿、文字数制限を超える投稿をして試してみることをお勧めします。
エラーメッセージがフォームの上に赤色で表示されればOKです。

これでエラーの処理から表示まで完了です。
次回の第4章（最終章）では、Bootstrapを使用して見栄えを良くする作業に入ります。

お疲れ様でした！