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

はじめに

フォームから送られるパラメータがモデルのバリデーションに引っかかった場合、エラー内容をビューに表示する必要があります。
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チュートリアルにも書いてある基本的な機能ですが、忘れやすいところですし、それだけでは不十分だったのでまとめてみました。