何に困っているか
ActiveRecordのvalidatesでエラーになった時に表示されるメッセージを%{attribute}%{message}
ではないフォーマットにしたい。
例えば、User.nameのvalidatesエラーでこの名前は1文字以上入力してください。
みたいなエラーメッセージを表示したい。
さらに、そのフォーマットを適用するスコープを限定したい。
validatesのエラーメッセージを変更するには
0. サンプルアプリの概要
今回の環境はruby:2.5.2
、rails:6.0.0
です。
Userモデルを持つアプリを用意します。
$ rails new validation_message_sample
$ rails g model user name:string
name
にvalidationを設定します。
class User < ApplicationRecord
validates :name, presence: true
end
この状態でvalidationエラーを発生させるとエラーメッセージはこうなります。
$ rails c
Running via Spring preloader in process 27748
Loading development environment (Rails 6.0.0)
irb(main):001:0> user = User.new
(2.0ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0> user.errors.full_messages
=> ["Name can't be blank"]
これをこの名前は1文字以上入力してください。
に変更します。
1. attribute名を日本語にする
まずはName
となっている箇所を名前
に変更します。
これにはrails-i18n
というgemを利用します。
Gemfileに以下を追記して、bundle install
を実行します。
gem 'rails-i18n`
次にconfig/application.rb
にi18nの設定を追加します。
module ValidationMessageSample
class Application < Rails::Application
config.load_defaults 6.0
# 下の2行を追加する
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s]
end
end
config/local/models/ja.yml
ファイルを作成し、変更したいattribute名を定義します。
ja:
activerecord:
models:
user: ユーザー
attributes:
user:
name: 名前
この状態で実行すると、以下のようになります。
$ rails c
Running via Spring preloader in process 28562
Loading development environment (Rails 6.0.0)
irb(main):001:0> user = User.new
(3.0ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0> user.errors.full_messages
=> ["名前を入力してください"]
attribute名が名前
になりました。
ついでにデフォルトのメッセージも日本語になりました。
2. :messageを利用する
:message
オプションを利用することで、メッセージを変更することができます。
詳細はRails Guideを参照してください。
class User < ApplicationRecord
validates :name, presence: { message: 'は1文字以上入力してください。' }
end
実行するとこうなりました。
$ rails c
Running via Spring preloader in process 29059
Loading development environment (Rails 6.0.0)
irb(main):001:0> user = User.new
(1.5ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0> user.errors.full_messages
=> ["名前は1文字以上入力してください。"]
では、attribute名の前にこの
という文字列を表示しようとしてみます。
class User < ApplicationRecord
validates :name, presence: { message: "この%{attribute}は1文字以上入力してください。" }
end
実行してみます。
$ rails c
Running via Spring preloader in process 29190
Loading development environment (Rails 6.0.0)
irb(main):001:0> user = User.new
(1.6ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0> user.errors.full_messages
=> ["名前この名前は1文字以上入力してください。"]
うーん、頭にもattribute名が付いてしまいます。
これは、エラーメッセージのデフォルトフォーマットが%{attribute}%{message}
となっているためです。
3. カスタムメソッドを利用する
カスタムメソッドを利用して、エラーメッセージを変更する。
errors.add(:カスタムメソッド名, エラーメッセージ)
でエラーメッセージを追加できますが、結局カスタムメソッド名が文頭に表示されてしまうので、:base
を利用することにします。
コードは以下の通り。
class User < ApplicationRecord
validate :name_presence
private
def name_presence
return if name.present?
errors.add(:base, "この名前は1文字以上入力してください.")
end
end
実行するとこうなります。
$ rails c
Running via Spring preloader in process 33134
Loading development environment (Rails 6.0.0)
irb(main):001:0> user = User.new
(2.5ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0> user.errors.full_messages
=> ["この名前は1文字以上入力してください."]
確かに意図通りにエラーメッセージを変更することはできました。
しかし、attribute名をわざわざベタ打ちしてしまっていてDRYじゃないし、本来の:base
の用途ではないので、あまり良い解ではないと考えます。
4. エラーメッセージのフォーマットを変更する
この記事によると、Rails6.0.0からエラーメッセージのフォーマットをスコープを限定して変更できるようになったようです。
スコープを限定するために以下の設定を追記します。
require_relative 'boot'
require 'rails/all'
Bundler.require(*Rails.groups)
module ValidationMessageSample
class Application < Rails::Application
config.load_defaults 6.0
config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml').to_s]
config.active_model.i18n_customize_full_message = true # この行を追加
end
end
config.active_model.i18n_customize_full_messageはデフォルトがfalseなので、この設定を実施しておかないと機能しません。(これに気づかずにハマりました・・・)
エラーメッセージのフォーマットを以下の通り設定します。
ja:
activerecord:
models:
user: ユーザー
attributes:
user:
name: 名前
errors:
models:
user:
attributes:
name:
format: '%{message}'
blank: この%{attribute}は1文字以上入力してください。
ちなみにUserモデルは下の通りです。
class User < ApplicationRecord
validates :name, presence: true
end
実行するとこうなります。
$ rails c
Running via Spring preloader in process 34119
Loading development environment (Rails 6.0.0)
irb(main):001:0> user = User.new
(2.4ms) SELECT sqlite_version(*)
=> #<User id: nil, name: nil, created_at: nil, updated_at: nil>
irb(main):002:0> user.valid?
=> false
irb(main):003:0> user.errors.full_messages
=> ["この名前は1文字以上入力してください。"]
遂に目的を達成する実装にたどり着くことが出来ました
一応、サンプルコードをGitHubに置いてます。
参考文献
https://qiita.com/Ushinji/items/242bfba84df7a5a67d5b
https://qiita.com/suketa/items/5af12acd88ebc8a2a1a5