Your friends at Viget present Extend, a Code & Technology Blog

Seven Useful ActiveModel Validators

Custom ActiveModel::Validators 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.


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.