この記事でやりたいこと
この記事では、Railsのフォームのエラーメッセージをまとめて出すのではなく、各インプットフィールドの近くに出すにはどうすればいいかやってみた結果を残しておきます。(やりたいことのイメージは下の方に結果画像あります。)
環境
この記事を書くにあたり、私の環境は以下の通りです。
- Ruby 2.7.1
- Rails 6.0.3
サンプルアプリの前提
今回は単純に「商品名(name)」と「値段(price)」を属性にもつ商品(Product)を管理するアプリを前提とします。
登録フォームで「商品名」と「値段」を入力できるようにしますが、それぞれのバリデーションに引っかかった場合にそれぞれのインプットフィールドに対してエラーメッセージを表示できるようにします。
また、一つのインプットフィールドに複数のバリデーションエラーメッセージが表示されるのも訳がわからなくなってしまうので、1つのエラーメッセージだけを表示するようにします。
それぞれの属性は以下のバリデーションを持っていることとします。
名前(name)
- 未入力はだめ。
- 20文字以内じゃないとだめ。
値段(price)
- 未入力はだめ。
- 100以上じゃないとだめ。
- 100000未満じゃないとだめ。
このProductモデルを管理するscaffoldをサンプルアプリとします。
$ rails g scaffold product name:string value:integer
$ rails db:migrate
class Product < ApplicationRecord
validates :name,
presence: true,
length: {maximum: 20}
validates :price,
numericality: {
greater_than_or_equal_to: 100,
less_than: 100000
}
end
このアプリでは、/products/new/
ページでname
とprice
の入力をミスるとページトップにエラーが表示されるようになっています。
エラーメッセージの表示については、app/views/products/_form.html.erb
の部分テンプレートで定義されています。
<%= form_with(model: product, local: true) do |form| %>
<%# ここからエラーメッセージ %>
<% if product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% product.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%# ここまでエラーメッセージ %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :price %>
<%= form.number_field :price %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
実装例
このapp/views/products/_form.html.erb
を編集して、name
のエラーメッセージはname
のインプットフィールドの下に、price
のエラーメッセージはprice
のインプットフィールドの下に表示するようにします。
現状のapp/views/products/_form.html.erb
では
-
product.errors.any?
で全ての属性のうち1つでもエラーがあったかどうかをチェック -
product.errors.full_messages
で全ての属性に関するエラーメッセージを表示
しています。
つまり、属性単位にエラー有無を判別して、属性単位にエラーメッセージを取得できればよさそうです。
属性単位にエラー有無を判別するinclude?
include?(attribute)
を用いることで属性単位のエラー有無を判別することができます。
attribute
に関するエラーがモデルオブジェクトのエラーオブジェクトに格納されている場合true
を、そうでない場合はfalse
を返却するメソッドです。
参考
属性単位にエラーメッセージを取得するfull_messages_for
full_messages
で全ての属性に関するエラーメッセージを取得できましたが、full_messages_for(attribute)
を使うことで特定の属性に関するエラーメッセージを取得することができます。
参考
これらを組み合わせることでapp/views/products/_form.html.erb
を以下のように編集します。
<%= form_with(model: product, local: true) do |form| %>
<% if product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2>
-
- <ul>
- <% product.errors.full_messages.each do |message| %>
- <li><%= message %></li>
- <% end %>
- </ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
+ <% if product.errors.include?(:name) %>
+ <p style="color: red;"><%= product.errors.full_messages_for(:name).first %>
+ <% end %>
</div>
<div class="field">
<%= form.label :price %>
<%= form.number_field :price %>
+ <% if product.errors.include?(:price) %>
+ <p style="color: red;"><%= product.errors.full_messages_for(:price).first %>
+ <% end %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
今回はfull_messages_for
のエラーメッセージの配列から1つ目のメッセージだけを表示するようにfirst
で配列の先頭のアイテムを取得しています。
この変更によってエラーメッセージの表示が以下のように変わります。
やりたいことができました。