# Generic @EachValidator@ that validates account names on various communication # services such as Skype, Yahoo!, etc. This class provides a simple DSL for # describing valid account names on these sites, performing all validation for # you. # # Subclass this class and use the provided DSL to describe account names on a # given site. An example for a fictional service called Talkalot: # #
# class TalkalotValidator < AccountNameValidator
# min_length 5
# max_length 64
# valid_chars "A-Z0-9_"
# end
#
#
# With your validator defined you can now use it in your Active Record models
# like any other @EachValidator@:
#
#
# validates :talkalot_id,
# talkalot: true
#
#
# This class automatically handles the following options passed to the
# @validates@ method:
#
# | @:allow_nil@ | Allows @nil@ values. |
# | @:message@ | Provide a custom error message. |
#
# Error messages generated by this class are stored in the translation table.
# The localization keys used are generated by the
# @ActiveModel::Errors#generate_message@ method (see its documentation for more
# information). The lastmost element of the localization key is the error
# message key. The error message key is a combination of a validator subclass's
# {.error_key_prefix} and the error key suffix for a given constraint.
#
# By default the error key prefix is the underscored
#
# As an example, for the @TalkalotValidator@ example above, the error message
# key used in the event that a two-letter account name is given would be
# @:talkalot_too_short@. If you wanted to override the prefix, you could do:
#
#
# class TalkalotValidator < AccountNameValidator
# error_key_prefix :talky
# end
#
#
# In this case the error message key for a two-letter account name would be
# @:talky_too_short@. (You'd do this if Talkalot accounts were called "talkies,"
# for example.)
#
# @abstract Subclass this validator to perform your specific account name
# validations.
class AccountNameValidator < ActiveModel::EachValidator
class_inheritable_array :validations
self.validations = Array.new
# @private
def validate_each(record, attribute, value)
return if options[:allow_nil] and value.nil?
return if options[:allow_blank] and value.blank?
return unless self.class.validations
self.class.validations.each { |block, key| record.errors.add(attribute, options[:message] || record.errors.generate_message(attribute, :"#{self.class.error_key_prefix}_#{key}")) if !block[value.to_s] }
end
protected
# @overload error_key_prefix
# Returns the prefix for error message keys used by this class.
# @return [Symbol] The error message key prefix.
#
# @overload error_key_prefix(value)
# Sets the error message key prefix this class uses.
# @param [Symbol] value The new error message key prefix.
def self.error_key_prefix(value=nil)
if value then
@error_key_prefix = value
else
return @error_key_prefix || to_s.demodulize.sub(/Validator$/, '').underscore.to_sym
end
end
# Describes a custom restraint on account names.
#
# @param [Symbol] key The error message key suffix to use.
# @yield [value] The custom validation.
# @yieldparam [String] value The value to validate. This will have been
# coerced into a @String@.
# @yieldreturn [true, false] Whether or not the validation succeeded.
def self.add_validation(key, &block)
return unless block_given?
self.validations << [ block, key ]
end
# Enforces a minimum length on account names. Uses the "too_short" error
# message key suffix.
#
# @param [Fixnum] num The minimum number of characters.
def self.min_length(num)
add_validation(:too_short) { |value| value.length >= num }
end
# Enforces a maximum length on account names. Uses the "too_long" error
# message key suffix.
#
# @param [Fixnum] num The maximum number of characters.
def self.max_length(num)
add_validation(:too_long) { |value| value.length <= num }
end
# Enforces a valid set of characters. Uses the "invalid_chars" error message
# key suffix.
#
# @param [String] charlist A set of valid characters, in regex character class
# format (e.g., "[A-Z0-9_]").
def self.valid_chars(charlist)
add_validation(:invalid_chars) { |value| value =~ /^[#{charlist}]+$/ }
end
# Enforces a valid set of characters for the first character of the account
# name. Uses the "invalid_first_char" error message key suffix.
#
# @param [String] charlist A set of valid characters, in regex character class
# format (e.g., "[A-Z0-9_]").
def self.first_char(charlist)
add_validation(:invalid_first_char) { |value| value[0] =~ /^[#{charlist}]$/ }
end
end