Seven Useful ActiveModel Validators
Zachary Porter, Former Senior Developer
Article Category:
Posted on
Custom ActiveModel::Validator
s are an easy way to validate individual attributes on your Rails models. All that's required is a Ruby class that inherits from ActiveModel::EachValidator
and implements a validate_each
method that takes three arguments: record, attribute, and value. I have written a few lately, so I pinged the rest of the amazingly talented Viget developers for some contributions. Here's what we came up with.
Simple URI Validator
A "simple URI" can be either a relative path or an absolute URL. In this case, any value that could be parsed by Ruby's URI
module is allowed:
class UriValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_uri?(value)
record.errors[attribute] << (options[:message] || 'is not a valid URI')
end
end
private
def valid_uri?(uri)
URI.parse(uri)
true
rescue URI::InvalidURIError
false
end
end
Full URL Validator
A "full URL" is defined as requiring a host and scheme. Ruby provides a regular expression to match against, so that's what is used in this validator:
class FullUrlValidator < ActiveModel::EachValidator
VALID_SCHEMES = %w(http https)
def validate_each(record, attribute, value)
unless value =~ URI::regexp(VALID_SCHEMES)
record.errors[attribute] << (options[:message] || 'is not a valid URL')
end
end
end
The Ruby regular expression can be seen as too permissive. For a stricter regular expression, Brian Landau shared this Github gist.
Email Validator
My good friends Lawson Kurtz and Mike Ackerman contributed the following email address validator:
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not a valid e-mail address")
end
end
end
If you'd rather validate by performing a DNS lookup, Brian Landau has you covered with this Github gist.
Secure Password Validator
Lawson provided this secure password validator (though credit goes to former Viget developer, James Cook):
class SecurePasswordValidator < ActiveModel::EachValidator
WORDS = YAML.load_file("config/bad_passwords.yml")
def validate_each(record, attribute, value)
if value.in?(WORDS)
record.errors.add(attribute, "is a common password. Choose another.")
end
end
end
Twitter Handle Validator
Lawson supplied this validator that checks for valid Twitter handles:
class TwitterHandleValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /^[A-Za-z0-9_]{1,15}$/
record.errors[attribute] << (options[:message] || "is not a valid Twitter handle")
end
end
end
Hex Color Validator
A validator that's useful when an attribute should be a hex color value:
class HexColorValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([a-f0-9]{3}){,2}\z/i
record.errors[attribute] << (options[:message] || 'is not a valid hex color value')
end
end
end
UPDATE: The regular expression has been simplified thanks to a comment from HappyNoff.
Regular Expression Validator
A great solution for attributes that should be a regular expression:
class RegexpValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless valid_regexp?(value)
record.errors[attribute] << (options[:message] || 'is not a valid regular expression')
end
end
private
def valid_regexp?(value)
Regexp.compile(value)
true
rescue RegexpError
false
end
end
Bonus Round
Replace all of those default error messages above with I18n translated strings for great justice. For the Regular Expression Validator above, the validate_each
method could look something like this:
def validate_each(record, attribute, value)
unless valid_regexp?(value)
default_message = record.errors.generate_message(attribute, :invalid_regexp)
record.errors[attribute] << (options[:message] || default_message)
end
end
Then the following could be added to config/locales/en.yml
:
en:
errors:
messages:
invalid_regexp: is not a valid regular expression
Now the default error messages can be driven by I18n.
Conclusion
We've found these to be very helpful at Viget. What do you think? Which validators do you find useful? Are there others worth sharing? Please share in the comments below.