フォーム処理時にバリデーションエラーが発生した場合の実装【日本語化・スタイル調整】

はじめに

フォームから送られるパラメータがモデルのバリデーションに引っかかった場合、エラー内容をビューに表示する必要があります。
Railsではそのための仕組みがあらかじめ用意されているので、比較的簡単に実装することが出来ます。
ただし、一部そのままでは使いにくい箇所があるので、そのあたりも含めてエラー時の処理をまとめたいと思います。

実装

なにはともあれエラー処理の基本を実装していきます。記事の新規作成時の処理をサンプルコードとしています。

コントローラー

ビューから送られてきたタイトルと本文を使ってレコードを新規作成します。
新規作成するときにモデルのバリデーションに引っかかった場合、レコードは作成されずエラー内容とともにビューに戻されます。

class ArticlesController < ApplicationController
  def create
    @article = Article.new(articles_params)
    if @article.save
      redirect_to articles_path
    else
      render :new
    end
  end

  private
    def articles_params
      params.require(:article).permit(:title, :content)
    end
end

余談ですが、エラー時にredirect_toではなくrenderを使っている理由は、renderはページ遷移時にコントローラーを介さないため、エラー内容を含んだパラメータをそのままビューに渡せるためです。
redirect_toを使ってしまうと、newメソッドが実行され、パラメータが上書きされることによりエラー内容がビューで参照できなくなってしまいます。

モデル

タイトルと本文に必須のバリデーションを実装します。

class Article < ApplicationRecord
  validates :title, presence: true
  validates :content, presence: true
end

ビュー

Rails5以降ではフォームの生成にform_withヘルパーメソッドの使用が推奨されています。form_forform_tagも使えますが、将来廃止される可能性もあるので使わないほうが無難でしょう。
form_withはデフォルトで非同期で処理を行うので、local: trueオプションを忘れないようにします。これを忘れると、コントローラーからエラー内容を受け取ることが出来ません。

_form_html.erb

<%= form_with model: @article, local: true do |form| %>
  <%= render 'shared/errors', { target: @article } %>
  ...
<% end %>

shared/_errors.html.erb

<% if target.errors.any? %>
  <div class="text-danger" role="alert">
    <h3>エラー項目があります</h3>
    <ul>
      <% target.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
    </ul>
  </div>
<% end %>

以上で基本的なエラー時の処理が実装できました。

日本語化

エラー内容はデフォルトでは英語でしか表示されないので日本語化します。

Gem追加

Gemfileに以下を追記してbundle installを行います。

gem 'rails-i18n'

application.rb変更

config/application.rbに以下を追記します。

...

module AppName
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # 以下を追記
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    config.i18n.default_locale = :ja
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]
  end
end

日本語化ファイル作成

config/locales/ja.ymlを作成し、以下を記述します。

ja:
  activerecord:
    attributes:
      article:
        title: タイトル
        content: 本文

以上でエラー内容の日本語化が出来ました。

スタイルを整える

エラーが発生しビューに戻ってくると、エラー項目を囲む.field_with_errorsというスタイルが自動的に付与されます。

...
  <div class="form-group row">
    <%= form.label :title, 'タイトル(必須)', class: 'col-12 col-sm-2 col-form-label' %>
    <div class="col-12 col-sm-10">
      <%= form.text_field :title, class: 'form-control', placeholder: 'タイトル' %>
    </div>
  </div>
...

エラーが発生すると以下のようになります。

...
  <div class="form-group row">
    <div class="field_with_errors">
      <%= form.label :title, 'タイトル(必須)', class: 'col-12 col-sm-2 col-form-label' %>
    </div>
    <div class="col-12 col-sm-10">
      <div class="field_with_errors">
        <%= form.text_field :title, class: 'form-control', placeholder: 'タイトル' %>
      </div>
    </div>
  </div>
...

Bootstrapを使用していると、自動付与された.field_with_errorsによりスタイルが崩れてしまいます。
これを回避するにはスタイルを上書きする必要があります。
app/assets/stylesheets/application.scssに以下を追記します。

...
.field_with_errors {
  display: contents;
  input, textarea {
    @extend .is-invalid;
  }
}
...

.field_with_errorsの表示を変更するとともに、内包するinputtextarea.is-invalidのスタイルを適用させています。
この書き方はBootstrapを使っている場合に限られますのでご了承ください。

まとめ

エラー時の処理はRailsチュートリアルにも書いてある基本的な機能ですが、忘れやすいところですし、それだけでは不十分だったのでまとめてみました。

関連記事

【Rails】Railsアップグレードまとめ
# はじめに Ruby on Railsに限らず、何らかのフレームワークを使ってWebシステムを構築している場合、フレームワークのアップグレード作業は避けて通れません。 一般的にフレームワークはバージョン毎にEOL (End of Life [...]
2022年10月1日 14:32
【Rails】ユーザー登録時に行うメールアドレス認証機能の実装方法
# はじめに ユーザー登録/解除やログイン/ログアウトといった認証機能の導入に「devise」というGemを使っている人は多いと思います。「devise」では以下のように記述するだけで、ユーザー登録時に確認メールを送付しメールアドレス認証を行う機 [...]
2022年9月24日 14:24
【Rails】モデルに列挙型(enum)を定義し、使いこなす方法
# はじめに Railsはモデルでカラム名と同名の列挙型(enum)を定義することで、カラムと列挙型の変数を紐付けることができます。カラムと列挙型の変数を紐付けると、カラムに対して様々な便利な使い方ができるようになります。 本記事では、モデ [...]
2022年9月3日 10:29
【Rails】RailsでCORSとPreflight requestの設定を行う方法
# はじめに RailsアプリをAPIサーバーとして構築するには、CORS (Cross-Origin Resource Sharing)と Preflight requestの設定を行う必要があります。APIサーバーは外部からの要求に対して処理 [...]
2022年8月27日 10:44
【Rails】M1チップ搭載MacでRuby on Railsの開発環境構築
# はじめに M1チップ搭載MacにRuby on Railsの開発環境を構築する手順を記載します。 - MacBook Air (M1, 2020) - macOS Monterey 12.3.1 # Homebrew ## [...]
2022年5月5日 11:56
【Rails】Rakeタスクの基本情報と作成・実行方法
# はじめに Railsには標準でRakeというGemが同梱されています。RakeはRubyで実装されたMake(UNIX系のOSで使用できるコマンド)のようなビルド作業を自動化するツールです。Ruby Make、略してRakeというわけですね。 [...]
2022年3月7日 22:12
【Rails】モデルに外部キーを設定する方法とよく起こるエラー内容について
# はじめに Railsでモデルに外部キーを設定する方法について説明します。 # モデルに外部キーを設定する ## リレーションシップ 今回は1つのブログ記事は複数のコメントを持つ1対多のリレーションシップを例に説明します。現在は` [...]
2022年2月10日 14:18
【Rails】Capybaraのfill_inメソッドを実行すると「既存レコードの内容+指定した内容」がセットされる事象の原因と対処【RSpec】
# はじめに RSpec + Capybaraを使用して、Railsアプリの統合テストを実装しています。とあるモデルの編集画面において、入力フォームの内容を書き換えた上で送信し、レコードが更新されることを確認します。 入力フォームの内容を書 [...]
2022年1月27日 21:22