# 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