I have attributes with special validation where I use the message clause to display a special message just for that validation. Here is one example:
validat
Use a symbol for the message:
validates :email, presence: true, length: { maximum: 60 },
format: { with: valid_email_regex, message: :bad_email },
uniqueness: { case_sensitive: false }
then in the yaml file
[lang]:
activerecord:
errors:
messages:
bad_email: "just ain't right"
If there's a translation specific to this model, it will override the general one above:
[lang]:
activerecord:
errors:
models:
model_name: # or namespace/model_name
attributes:
email:
bad_email: "model-specific message for invalid email"
If you write custom validations, add_error(:email, :bad_email)
will do the lookup above, but errors[:email] << :bad_email
will not.
The solution to this does not need to be custom; The format
validator message already maps to the :invalid
symbol. You need only set the invalid in translation.
en:
activerecord:
errors:
models:
some_model:
attributes:
email:
invalid: "FOO"
Reference: http://edgeguides.rubyonrails.org/i18n.html#error-message-interpolation
I just walked through all this and found the rails guides for custom validators too hard-coded... I'm posting this here even though it's not exactly what you asked, but the Q title fits perfectly (which is why I read this post for my problem).
Custom validation with i18n message:
validate :something_custom?, if: :some_trigger_condition
def something_custom?
if some_error_condition
errors.add(:some_field_key, :some_custom_msg)
end
end
# en.yml
activerecord:
errors:
models:
some_model:
some_custom_msg: "This is i18n controlled. yay!"
I will give a complete example.
To make your models cleaner, you can create a custom validation helper method in an entirely new directory - app/validations
, which will be autoloaded by Rails.
I'll call my file time_validator.rb
, located at app/validations/time_validator.rb
My file has the following code, which validates user-entered time - that it is actually time.
# frozen_string_literal: true
class TimeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
Time.parse(value).strftime('%H:%M')
rescue ArgumentError
record.errors.add(attribute, :invalid_time)
end
end
You can read more about creating custom ActiveRecord validations here
Our main point of concern is the record.errors
line. Note that it's attribute
and not :attribute
, where attribute
is the column in your model.
:invalid_time
is the key that retrieves your translated content from your locales file. In my case, this is the en
file:
en
activerecord:
errors:
models:
home:
attributes:
check_in_time:
invalid_time: Please enter valid time
check_out_time:
invalid_time: Please enter valid time
Then in the home
model:
validates :check_in_time, time: { allow_blank: true }
validates :check_out_time, time: { allow_blank: true }
time
automatically gets mapped to the class TimeValidator
, and the methods therein get run.
Incase this is violated, ActiveRecord will throw an error right below the column name.
Hope this helps!