Skip to content
This repository
branch: master
file 199 lines (182 sloc) 6.742 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
require "active_support/core_ext/module/anonymous"

module ActiveModel

  # == Active \Model \Validator
  #
  # A simple base class that can be used along with
  # ActiveModel::Validations::ClassMethods.validates_with
  #
  # class Person
  # include ActiveModel::Validations
  # validates_with MyValidator
  # end
  #
  # class MyValidator < ActiveModel::Validator
  # def validate(record)
  # if some_complex_logic
  # record.errors[:base] = "This record is invalid"
  # end
  # end
  #
  # private
  # def some_complex_logic
  # # ...
  # end
  # end
  #
  # Any class that inherits from ActiveModel::Validator must implement a method
  # called +validate+ which accepts a +record+.
  #
  # class Person
  # include ActiveModel::Validations
  # validates_with MyValidator
  # end
  #
  # class MyValidator < ActiveModel::Validator
  # def validate(record)
  # record # => The person instance being validated
  # options # => Any non-standard options passed to validates_with
  # end
  # end
  #
  # To cause a validation error, you must add to the +record+'s errors directly
  # from within the validators message.
  #
  # class MyValidator < ActiveModel::Validator
  # def validate(record)
  # record.errors.add :base, "This is some custom error message"
  # record.errors.add :first_name, "This is some complex validation"
  # # etc...
  # end
  # end
  #
  # To add behavior to the initialize method, use the following signature:
  #
  # class MyValidator < ActiveModel::Validator
  # def initialize(options)
  # super
  # @my_custom_field = options[:field_name] || :first_name
  # end
  # end
  #
  # Note that the validator is initialized only once for the whole application
  # life cycle, and not on each validation run.
  #
  # The easiest way to add custom validators for validating individual attributes
  # is with the convenient <tt>ActiveModel::EachValidator</tt>.
  #
  # class TitleValidator < ActiveModel::EachValidator
  # def validate_each(record, attribute, value)
  # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
  # end
  # end
  #
  # This can now be used in combination with the +validates+ method
  # (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
  #
  # class Person
  # include ActiveModel::Validations
  # attr_accessor :title
  #
  # validates :title, presence: true
  # end
  #
  # It can be useful to access the class that is using that validator when there are prerequisites such
  # as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor.
  # To setup your validator override the constructor.
  #
  # class MyValidator < ActiveModel::Validator
  # def initialize(options={})
  # super
  # options[:class].send :attr_accessor, :custom_attribute
  # end
  # end
  class Validator
    attr_reader :options

    # Returns the kind of the validator.
    #
    # PresenceValidator.kind # => :presence
    # UniquenessValidator.kind # => :uniqueness
    def self.kind
      @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
    end

    # Accepts options that will be made available through the +options+ reader.
    def initialize(options = {})
      @options = options.except(:class).freeze
      deprecated_setup(options)
    end

    # Returns the kind for this validator.
    #
    # PresenceValidator.new.kind # => :presence
    # UniquenessValidator.new.kind # => :uniqueness
    def kind
      self.class.kind
    end

    # Override this method in subclasses with validation logic, adding errors
    # to the records +errors+ array where necessary.
    def validate(record)
      raise NotImplementedError, "Subclasses must implement a validate(record) method."
    end

    private
    def deprecated_setup(options) # TODO: remove me in 4.2.
      return unless respond_to?(:setup)
      ActiveSupport::Deprecation.warn "The `Validator#setup` instance method is deprecated and will be removed on Rails 4.2. Do your setup in the constructor instead:

class MyValidator < ActiveModel::Validator
def initialize(options={})
super
options[:class].send :attr_accessor, :custom_attribute
end
end
"
      setup(options[:class])
    end
  end

  # +EachValidator+ is a validator which iterates through the attributes given
  # in the options hash invoking the <tt>validate_each</tt> method passing in the
  # record, attribute and value.
  #
  # All Active Model validations are built on top of this validator.
  class EachValidator < Validator #:nodoc:
    attr_reader :attributes

    # Returns a new validator instance. All options will be available via the
    # +options+ reader, however the <tt>:attributes</tt> option will be removed
    # and instead be made available through the +attributes+ reader.
    def initialize(options)
      @attributes = Array(options.delete(:attributes))
      raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
      super
      check_validity!
    end

    # Performs validation on the supplied record. By default this will call
    # +validates_each+ to determine validity therefore subclasses should
    # override +validates_each+ with validation logic.
    def validate(record)
      attributes.each do |attribute|
        value = record.read_attribute_for_validation(attribute)
        next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
        validate_each(record, attribute, value)
      end
    end

    # Override this method in subclasses with the validation logic, adding
    # errors to the records +errors+ array where necessary.
    def validate_each(record, attribute, value)
      raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
    end

    # Hook method that gets called by the initializer allowing verification
    # that the arguments supplied are valid. You could for example raise an
    # +ArgumentError+ when invalid options are supplied.
    def check_validity!
    end
  end

  # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
  # and call this block for each attribute being validated. +validates_each+ uses this validator.
  class BlockValidator < EachValidator #:nodoc:
    def initialize(options, &block)
      @block = block
      super
    end

    private

    def validate_each(record, attribute, value)
      @block.call(record, attribute, value)
    end
  end
end
Something went wrong with that request. Please try again.