Skip to content
This repository
branch: master
file 387 lines (364 sloc) 13.048 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 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'

module ActiveModel

  # == Active \Model \Validations
  #
  # Provides a full validation framework to your objects.
  #
  # A minimal implementation could be:
  #
  # class Person
  # include ActiveModel::Validations
  #
  # attr_accessor :first_name, :last_name
  #
  # validates_each :first_name, :last_name do |record, attr, value|
  # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
  # end
  # end
  #
  # Which provides you with the full standard validation stack that you
  # know from Active Record:
  #
  # person = Person.new
  # person.valid? # => true
  # person.invalid? # => false
  #
  # person.first_name = 'zoolander'
  # person.valid? # => false
  # person.invalid? # => true
  # person.errors.messages # => {first_name:["starts with z."]}
  #
  # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
  # method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
  # object, so there is no need for you to do this manually.
  module Validations
    extend ActiveSupport::Concern

    included do
      extend ActiveModel::Callbacks
      extend ActiveModel::Translation

      extend HelperMethods
      include HelperMethods

      attr_accessor :validation_context
      define_callbacks :validate, scope: :name

      class_attribute :_validators
      self._validators = Hash.new { |h,k| h[k] = [] }
    end

    module ClassMethods
      # Validates each attribute against a block.
      #
      # class Person
      # include ActiveModel::Validations
      #
      # attr_accessor :first_name, :last_name
      #
      # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
      # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
      # end
      # end
      #
      # Options:
      # * <tt>:on</tt> - Specifies the contexts where this validation is active.
      # You can pass a symbol or an array of symbols.
      # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or
      # <tt>on: [:create, :custom_validation_context]</tt>)
      # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
      # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
      # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
      # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
      # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
      # proc or string should return or evaluate to a +true+ or +false+ value.
      # * <tt>:unless</tt> - Specifies a method, proc or string to call to
      # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
      # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
      # method, proc or string should return or evaluate to a +true+ or +false+
      # value.
      def validates_each(*attr_names, &block)
        validates_with BlockValidator, _merge_attributes(attr_names), &block
      end

      # Adds a validation method or block to the class. This is useful when
      # overriding the +validate+ instance method becomes too unwieldy and
      # you're looking for more descriptive declaration of your validations.
      #
      # This can be done with a symbol pointing to a method:
      #
      # class Comment
      # include ActiveModel::Validations
      #
      # validate :must_be_friends
      #
      # def must_be_friends
      # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
      # end
      # end
      #
      # With a block which is passed with the current record to be validated:
      #
      # class Comment
      # include ActiveModel::Validations
      #
      # validate do |comment|
      # comment.must_be_friends
      # end
      #
      # def must_be_friends
      # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
      # end
      # end
      #
      # Or with a block where self points to the current record to be validated:
      #
      # class Comment
      # include ActiveModel::Validations
      #
      # validate do
      # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
      # end
      # end
      #
      # Options:
      # * <tt>:on</tt> - Specifies the contexts where this validation is active.
      # You can pass a symbol or an array of symbols.
      # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt> or
      # <tt>on: [:create, :custom_validation_context]</tt>)
      # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
      # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
      # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
      # proc or string should return or evaluate to a +true+ or +false+ value.
      # * <tt>:unless</tt> - Specifies a method, proc or string to call to
      # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
      # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
      # method, proc or string should return or evaluate to a +true+ or +false+
      # value.
      def validate(*args, &block)
        options = args.extract_options!
        if options.key?(:on)
          options = options.dup
          options[:if] = Array(options[:if])
          options[:if].unshift lambda { |o|
            Array(options[:on]).include?(o.validation_context)
          }
        end
        args << options
        set_callback(:validate, *args, &block)
      end

      # List all validators that are being used to validate the model using
      # +validates_with+ method.
      #
      # class Person
      # include ActiveModel::Validations
      #
      # validates_with MyValidator
      # validates_with OtherValidator, on: :create
      # validates_with StrictValidator, strict: true
      # end
      #
      # Person.validators
      # # => [
      # # #<MyValidator:0x007fbff403e808 @options={}>,
      # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
      # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
      # # ]
      def validators
        _validators.values.flatten.uniq
      end

      # Clears all of the validators and validations.
      #
      # Note that this will clear anything that is being used to validate
      # the model for both the +validates_with+ and +validate+ methods.
      # It clears the validators that are created with an invocation of
      # +validates_with+ and the callbacks that are set by an invocation
      # of +validate+.
      #
      # class Person
      # include ActiveModel::Validations
      #
      # validates_with MyValidator
      # validates_with OtherValidator, on: :create
      # validates_with StrictValidator, strict: true
      # validate :cannot_be_robot
      #
      # def cannot_be_robot
      # errors.add(:base, 'A person cannot be a robot') if person_is_robot
      # end
      # end
      #
      # Person.validators
      # # => [
      # # #<MyValidator:0x007fbff403e808 @options={}>,
      # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
      # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
      # # ]
      #
      # If one runs <tt>Person.clear_validators!</tt> and then checks to see what
      # validators this class has, you would obtain:
      #
      # Person.validators # => []
      #
      # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
      # so that:
      #
      # Person._validate_callbacks.empty? # => true
      #
      def clear_validators!
        reset_callbacks(:validate)
        _validators.clear
      end

      # List all validators that are being used to validate a specific attribute.
      #
      # class Person
      # include ActiveModel::Validations
      #
      # attr_accessor :name , :age
      #
      # validates_presence_of :name
      # validates_inclusion_of :age, in: 0..99
      # end
      #
      # Person.validators_on(:name)
      # # => [
      # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
      # # ]
      def validators_on(*attributes)
        attributes.flat_map do |attribute|
          _validators[attribute.to_sym]
        end
      end

      # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
      #
      # class Person
      # include ActiveModel::Validations
      #
      # attr_accessor :name
      # end
      #
      # User.attribute_method?(:name) # => true
      # User.attribute_method?(:age) # => false
      def attribute_method?(attribute)
        method_defined?(attribute)
      end

      # Copy validators on inheritance.
      def inherited(base) #:nodoc:
        dup = _validators.dup
        base._validators = dup.each { |k, v| dup[k] = v.dup }
        super
      end
    end

    # Clean the +Errors+ object if instance is duped.
    def initialize_dup(other) #:nodoc:
      @errors = nil
      super
    end

    # Returns the +Errors+ object that holds all information about attribute
    # error messages.
    #
    # class Person
    # include ActiveModel::Validations
    #
    # attr_accessor :name
    # validates_presence_of :name
    # end
    #
    # person = Person.new
    # person.valid? # => false
    # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
    def errors
      @errors ||= Errors.new(self)
    end

    # Runs all the specified validations and returns +true+ if no errors were
    # added otherwise +false+.
    #
    # Aliased as validate.
    #
    # class Person
    # include ActiveModel::Validations
    #
    # attr_accessor :name
    # validates_presence_of :name
    # end
    #
    # person = Person.new
    # person.name = ''
    # person.valid? # => false
    # person.name = 'david'
    # person.valid? # => true
    #
    # Context can optionally be supplied to define which callbacks to test
    # against (the context is defined on the validations using <tt>:on</tt>).
    #
    # class Person
    # include ActiveModel::Validations
    #
    # attr_accessor :name
    # validates_presence_of :name, on: :new
    # end
    #
    # person = Person.new
    # person.valid? # => true
    # person.valid?(:new) # => false
    def valid?(context = nil)
      current_context, self.validation_context = validation_context, context
      errors.clear
      run_validations!
    ensure
      self.validation_context = current_context
    end

    alias_method :validate, :valid?

    # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
    # added, +false+ otherwise.
    #
    # class Person
    # include ActiveModel::Validations
    #
    # attr_accessor :name
    # validates_presence_of :name
    # end
    #
    # person = Person.new
    # person.name = ''
    # person.invalid? # => true
    # person.name = 'david'
    # person.invalid? # => false
    #
    # Context can optionally be supplied to define which callbacks to test
    # against (the context is defined on the validations using <tt>:on</tt>).
    #
    # class Person
    # include ActiveModel::Validations
    #
    # attr_accessor :name
    # validates_presence_of :name, on: :new
    # end
    #
    # person = Person.new
    # person.invalid? # => false
    # person.invalid?(:new) # => true
    def invalid?(context = nil)
      !valid?(context)
    end

    # Hook method defining how an attribute value should be retrieved. By default
    # this is assumed to be an instance named after the attribute. Override this
    # method in subclasses should you need to retrieve the value for a given
    # attribute differently:
    #
    # class MyClass
    # include ActiveModel::Validations
    #
    # def initialize(data = {})
    # @data = data
    # end
    #
    # def read_attribute_for_validation(key)
    # @data[key]
    # end
    # end
    alias :read_attribute_for_validation :send

  protected

    def run_validations! #:nodoc:
      run_callbacks :validate
      errors.empty?
    end
  end
end

Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
Something went wrong with that request. Please try again.