112
90

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ActiveRecordのvalidatesで表示されるエラーメッセージのフォーマットを変更する

Last updated at Posted at 2019-09-09

何に困っているか

ActiveRecordのvalidatesでエラーになった時に表示されるメッセージを%{attribute}%{message}ではないフォーマットにしたい。
例えば、User.nameのvalidatesエラーでこの名前は1文字以上入力してください。みたいなエラーメッセージを表示したい。
さらに、そのフォーマットを適用するスコープを限定したい。

validatesのエラーメッセージを変更するには

0. サンプルアプリの概要

今回の環境はruby:2.5.2rails: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を実行します。

Gemfile
gem 'rails-i18n`

次にconfig/application.rbにi18nの設定を追加します。

config/application.rb

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名を定義します。

config/local/models/ja.yml
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名の前にこのという文字列を表示しようとしてみます。

app/models/user.rb
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を利用することにします。
コードは以下の通り。

app/models/user.rb
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なので、この設定を実施しておかないと機能しません。(これに気づかずにハマりました・・・)

エラーメッセージのフォーマットを以下の通り設定します。

config/local/models/ja.yml
ja:
  activerecord:
    models:
      user: ユーザー
    attributes:
      user:
        name: 名前
    errors:
      models:
        user:
          attributes:
            name:
              format: '%{message}'
              blank: この%{attribute}は1文字以上入力してください。

ちなみにUserモデルは下の通りです。

app/models/user.rb
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文字以上入力してください。"]

遂に目的を達成する実装にたどり着くことが出来ました :star2:

一応、サンプルコードをGitHubに置いてます。

参考文献

https://qiita.com/Ushinji/items/242bfba84df7a5a67d5b
https://qiita.com/suketa/items/5af12acd88ebc8a2a1a5

112
90
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
112
90

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?