April 22, 2019
This blog is part of our Rails 6 series.
Before Rails 6, the default format %{attribute} %{message}
is used to display
validation error message for a model's attribute.
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:title, "cannot be blank")
> > => "Title cannot be blank"
> >
The default format can be overridden globally using a language-specific locale file.
# config/locales/en.yml
en:
errors:
format:
"'%{attribute}' %{message}"
With this change, the full error message is changed for all the attributes of all models.
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:title, "cannot be blank")
> > => "'Title' cannot be blank"
> > user = User.new
> > => #<User id: nil, first_name: nil, last_name: nil, country: nil, created_at: nil, updated_at: nil>
> > user.errors.full_message(:first_name, "cannot be blank")
> > => "'First name' cannot be blank"
> >
This trick works in some cases but it doesn't work if we have to customize the error messages on the basis of specific models or attributes.
Before Rails 6, there is no easy way to generate error messages like shown below.
The article's title cannot be empty
or
First name of a person cannot be blank
If we change the errors.format
to The article's %{attribute} %{message}
in
config/locales/en.yml
then that format will be unexpectedaly used for other
models, too.
# config/locales/en.yml
en:
errors:
format:
"The article's %{attribute} %{message}"
This is what will happen if we make such a change.
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:title, "cannot be empty")
> > => "The article's Title cannot be empty"
> > user = User.new
> > => #<User id: nil, first_name: nil, last_name: nil, country: nil, created_at: nil, updated_at: nil>
> > user.errors.full_message(:first_name, "cannot be blank")
> > => "The article's First name cannot be blank"
> >
Notice the error message generated for the :first_name
attribute of User
model. This does not look the way we want, right?
Let's see what is changed in Rails 6 to overcome this problem.
ActiveModel::Errors#full_message
in Rails 6Overriding the format of error message globally using errors.format
is still
supported in Rails 6.
In addition to that, Rails 6 now also supports overriding the error message's format at the model level and at the attribute level.
In order to enable this support, we need to explicitly set
config.active_model.i18n_customize_full_message
to true
in the Rails
configuration file, preferably in config/application.rb
which is implicitly
set to false
by default.
We can customize the full error message format for each model separately.
# config/locales/en.yml
en:
activerecord:
errors:
models:
article:
format: "`%{attribute}`: %{message}"
user:
format: "%{attribute} of the user %{message}"
The full error messages will look like this.
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:title, "cannot be empty")
> > => "`Title`: cannot be empty"
> > article.valid?
> > => false
> > article.errors.full_messages
> > => ["`Title`: can't be blank"]
> > user = User.new
> > => #<User id: nil, first_name: nil, last_name: nil, country: nil, created_at: nil, updated_at: nil>
> > user.errors.full_message(:first_name, "cannot be blank")
> > => "First name of the user cannot be blank"
> > comment = Comment.new
> > => #<Comment id: nil, message: nil, author_id: nil, created_at: nil, updated_at: nil>
> > comment.errors.full_message(:message, "is required")
> > => "Message is required"
> >
Notice how the default format %{attribute} %{message}
is used for generating
the full error messages for the Comment
model since its format is not being
overridden.
Since the other methods such as ActiveModel::Errors#full_messages
,
ActiveModel::Errors#full_messages_for
, ActiveModel::Errors#to_hash
etc. use
the ActiveModel::Errors#full_message
method under the hood, we get the full
error messages according to the custom format in the returned values of these
methods respectively as expected.
Similar to customizing format at the model level, we can customize the error format for specific attributes of individual models.
# config/locales/en.yml
en:
activerecord:
errors:
models:
article:
attributes:
title:
format: "The article's title %{message}"
user:
attributes:
first_name:
format: "%{attribute} of a person %{message}"
With such a configuration, we get the customized error message for the title
attribute of the Article model.
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:title, "cannot be empty")
> > => "The article's title cannot be empty"
> > article.errors.full_message(:description, "cannot be empty")
> > => "Description cannot be empty"
> > user = User.new
> > => #<User id: nil, first_name: nil, last_name: nil, country: nil, created_at: nil, updated_at: nil>
> > user.errors.full_message(:first_name, "cannot be blank")
> > => "First name of a person cannot be blank"
> > user.errors.full_message(:last_name, "cannot be blank")
> > => "Last name cannot be blank"
> >
Note that the error messages for the rest of the attributes were generated using
the default %{attribute} %{message}
format for which we didn't add custom
formats in the config/locales/en.yml
manifest.
# config/locales/en.yml
en:
activerecord:
errors:
models:
article/comments/attachments:
format: "%{message}"
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:'comments/attachments.file_name', "is required")
> > => "is required"
> > article.errors.full_message(:'comments/attachments.path', "cannot be blank")
> > => "cannot be blank"
> > article.errors.full_message(:'comments.message', "cannot be blank")
> > => "Comments message cannot be blank"
> >
# config/locales/en.yml
en:
activerecord:
errors:
models:
article/comments/attachments:
attributes:
file_name:
format: "File name of an attachment %{message}"
> > article = Article.new
> > => #<Article id: nil, title: nil, description: nil, created_at: nil, updated_at: nil>
> > article.errors.full_message(:'comments/attachments.file_name', "is required")
> > => "File name of an attachment is required"
> > article.errors.full_message(:'comments/attachments.path', "cannot be blank")
> > => "Comments/attachments path cannot be blank"
> >
The custom formats specified in the locale file has the following precedence in the high to low order.
activerecord.errors.models.article/comments/attachments.attributes.file_name.format
activerecord.errors.models.article/comments/attachments.format
activerecord.errors.models.article.attributes.title.format
activerecord.errors.models.article.format
errors.format
To learn more, please checkout rails/rails#32956 and rails/rails#35789.
If this blog was helpful, check out our full blog archive.