Custom validation errors in Rails

How to Customize Rails Validation Errors to Remove Leading Attribute Column Names

Rails validations is an elegant way to verify the model state before it's saved into the database. Often, you want to provide a custom, user-friendly error message to the user. In this post, we'll learn how to accomplish this with custom validation methods.

3 min read

Let's consider the following model in Rails:

class Post < ApplicationRecord
  validates :title, presence: true
end

When Rails runs the validations while creating or updating the post record, if the title attribute is missing, Rails will add the following error message to the model: "Title can't be blank".

Specifically, Rails inserts the column name (attribute) at the beginning of the error message.

You can customize this error by passing the message option to the validation:

validates :title, presence: { message: "must be provided" }

This results in the following error message: "Title must be provided".

What if you don't want the column name "Title" at the start of the error message? This might be the case when you want fully custom error message, like "You must provide a valid title".

💡
Solution: To create a custom error message without the leading attribute column name, create a custom validation and add your message to the errors collection on the model's base attribute.

A custom validation method verifies the model state, and if it's invalid, the method adds the error message to the errors collection on the model.

class Post < ApplicationRecord
  validate :title_must_be_present

  def title_must_be_present
    errors.add(:base, "You must provide a valid title") unless title.present?
  end
end

The key difference between adding error to :base instead of :title is that it relates to the model's state as a whole, instead of the specific attribute.

Multiple Custom Validations

Using custom validation methods also let you perform more involved validations on the model. For example, imagine you want to ensure that the title is present and is between 5 to 20 characters long. However, you want to show the length error only when they have provided the title.

Consider this validation:

class Post < ApplicationRecord
  validates :title, presence: true, length: { in: 5..20 }
end

When the title is missing, you will get the following error message: "Title can't be blank, Title is too short (minimum is 6 characters)".

Although it's technically correct, the length error is irrelevant when the title is missing entirely. In such cases, you can write a custom validation method as follows:

class Post < ApplicationRecord
  validate :title_must_be_valid

  private

    def title_must_be_valid
      errors.add(:base, "You must provide a title") and return unless title.present?

      # title is present but invalid
      errors.add(:base, "Title length must be within 5-20 characters") unless title.length.between?(5, 20)
    end
end

Now, if the title is missing, we'll only get the error "You must provide a title" and when the title is too short or too long, we'll get the error "Title length must be within 5-20 characters".

Very cool.

Update: One of the subscribers of this blog, Goulven Champenois, provided his feedback to this post and showed a better way to accomplish this. I haven't yet had a chance to play with his solution, so copy + pasting his message below, but I will update the post after I try this solution and see how it solves the problem.

The trick you provide to remove title from the error message works, but at the expense of being able to indicate which field must be corrected in the form.

This comes particularly handy to highlight the field, display the error message next to it, or whatever you might want to do. Doing this is therefore bad for UX, and bad for accessibility. 

A better solution is to override the way error messages are being generated. This can be done for every error message, or just those for a given model, or even just for one of the model’s attributes. 

To do that, you first need to set config.active_model.i18n_customize_full_message to true in an initializer, then open your locale files and redefine either en.errors.format,  en.activerecord.errors.models.[model_name].format, or en.activerecord.errors.models.[model_name].attributes.[attribute].format

This is available since Rails 6, and explained in this post.

Thanks, Goulven!


That's a wrap. I hope you found this article helpful and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.