# Validations based on negations of regular expressions.
# validates_not_format_of takes an array of attributes,
#  a regular expression to apply to them,
#  and an optional hash of configuration options.
#
# All other validations take an array of attributes,
#  a word or list of words to act upon,
#  and an optional hash of configuration options.
#
# Examples:
# validates_does_have_the_words :username, :description, %w(chicken yellow coward), :ignore_case => true
# In this case, the model will not be valid if the username or description attributes contain any of the
# restricted words, regardless of case.
#
module ActiveRecord
  module Validations
    module ClassMethods
 
      # Negates a regular expression match against an attribute.
      # Similar to grep -v
      #
      def validates_not_format_of(*attr_names)
        configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
        configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
 
        raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
 
        validates_each(attr_names, configuration) do |record, attr_name, value|
          record.errors.add(attr_name, configuration[:message]) if value.to_s =~ configuration[:with]
        end
      end
 
      def validates_does_not_begin_with_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| "^#{word}" }
      end
      alias :validates_does_not_begin_with_the_words :validates_does_not_begin_with_the_word
 
      # Validates an attribute does not end with the provided string, or strings.
      # Examples:
      # validates_does_not_end_with_the_word :username, '2000'
      #  or
      # validates_does_not_end_with_the_words :username, %w(xxx ooo 666 McFly)
      #
      def validates_does_not_end_with_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| "#{word}$" }
      end
      alias :validates_does_not_end_with_the_words :validates_does_not_end_with_the_word
 
      # Validates an attribute does not contain disallowed words
      # Examples:
      # validates_does_not_have_the_word :username, 'admin'
      #  or
      # validates_does_not_have_the_words :username, %w(admin guest moderator)
      #
      def validates_does_not_have_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| word }
      end
      alias :validates_does_not_have_the_words :validates_does_not_have_the_word
 
      def validates_is_not_the_word(*attr_names)
        validates_attributes_not_format_of_block(attr_names) { |word| "^#{word}$" }
      end
      alias :validates_is_not_the_words :validates_is_not_the_word
 
      private
      # The block supplied to validates_attributes_not_format_of_block must yield a string
      # that can be converted into a regular expression by Regexp.new
      #
      # The argument supplied to the block is a string that has been regular-expression-quoted,
      # and is safe to use without further escaping.
      #
      # You can pass options for how the regular expression is created in the final options hash.
      # Either use :ignore_case => true
      #  or
      # :regexp_options => Fixnum
      # either of which will be passed as the 2nd argument to Regexp.new, with :regexp_options taking precedence
      # See documentation on Regexp.new for more information on allowable values for :regexp_options
      #
      def validates_attributes_not_format_of_block(attr_names, &block)
        options = attr_names.last.is_a?(Hash) ? attr_names.pop : { }
        word = attr_names.pop
 
        words = word.respond_to?(:each) ? word : [ word ]
        regexp_options = options.delete(:regexp_options) || options.delete(:ignore_case)
 
        restriction = Regexp.new(words.map { |w| "(#{yield Regexp.quote(w.to_s)})" }.join('|'), regexp_options)
 
        configuration = {
          # Overridable default configuration
          :message => 'is not valid',
          :on => :save
        }.merge(options || {}).merge(
          # Mandatory default configuration
          :with => restriction
        )
        validates_not_format_of attr_names, configuration
      end
    end
  end
end

Leave a Comment

Enclose code in <code lang="ruby"></code> if you care.
Preview your comment using the button below.