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".
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 removetitle
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 setconfig.active_model.i18n_customize_full_message
to true in an initializer, then open your locale files and redefine eitheren.errors.format
,en.activerecord.errors.models.[model_name].format
, oren.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.