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.