Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- module ActiveModel
- module Validations
- def self.included(base)
- base.class_eval do
- @validation_stack = {
- :any => [],
- :create => [],
- :update => []
- }.freeze
- include InstanceMethods
- extend ClassMethods
- end
- end
- module InstanceMethods
- NEW_RECORD_NOT_IMPLEMENTED = "You need to implement a new_record? instance method in your class."
- # The list of errors.
- def errors
- @errors ||= Errors.new
- end
- # Runs all the validations
- def validate
- errors.clear
- self.class.validate(self)
- end
- # Checks if there are any errors
- def valid?
- errors.empty? && errors.base.empty?
- end
- # Should we run validations for create or update?
- def validation_scope
- raise NoMethodError, NEW_RECORD_NOT_IMPLEMENTED unless respond_to?(:new_record?)
- new_record? ? :create : :update
- end
- end
- module ClassMethods
- MESSAGES = {
- :blank => "can not be blank",
- :format => "is of invalid format"
- }
- # Runs the actual validations, one by one.
- def validate(instance)
- # Runs the validations for the current validation scope (create or update)
- @validation_stack[instance.validation_scope].each {|validation| validation.run(instance) }
- # Runs the validations that runs on both create and update
- @validation_stack[:any].each {|validation| validation.run(instance) }
- end
- # Validates that the value of an attribute isn't "blank?".
- #
- # Post.validates_presence_of :title
- def validates_presence_of(*args)
- options = args.options :message => MESSAGES[:blank]
- validate_attributes(args, options) do |record, attribute, value|
- record.errors[attribute] << options[:message] if value.blank?
- end
- end
- # Regular expression matching.
- #
- # User.validates_format_of :email, :with => /^.+@.+\..+$/
- def validates_format_of(*args)
- options = args.options :message => MESSAGES[:format]
- options.assert_required_keys :with
- validate_attributes(args, options) do |record, attribute, value|
- record.errors[attribute] << options[:message] unless value =~ options[:with]
- end
- end
- def validates_length_of(*args)
- # TODO: :minimum and :maximum, to complement :length
- options = args.options :message => MESSAGES[:length]
- options.assert_required_keys :length
- validate_attributes(args, options) do |record, attribute, value|
- record.errors[attribute] << options[:message] if value < options[:length]
- end
- end
- def validates_numericality_of(*args)
- # TODO: Use something other than Integer(value) and rescue.
- # NOTE: This method is pretty nasty in AR. I think AR should handle the oddities in
- # order to make the change backwards compatible.
- options = args.options :message => MESSAGES[:numericality]
- validate_attributes(args, options) do |record, attribute, value|
- record.errors[attribute] << options[:message] unless Integer(value) rescue(false)
- end
- end
- # Uses Enumerable#in? to determine if the value matches the items in the :in option.
- #
- # User.validates_inclusion_of :gener, :in => %(male female)
- def validates_inclusion_of(*args)
- options = args.options :message => MESSAGES[:inclusion]
- options.assert_required_keys :in
- validate_attributes(args, options) do |record, attribute, value|
- record.errors[attribute] << options[:message] unless options[:in].include?(value)
- end
- end
- # The opposite of validates_inclusion_of.
- def validates_exclusion_of(*args)
- options = args.options :message => MESSAGES[:exclution]
- options.assert_required_keys :in
- validate_attributes(args, options) do |record, attribute, value|
- record.errors[attribute] << options[:message] unless options[:in].include?(value)
- end
- end
- # Validate attribute names with the block passed as the actual validation.
- #
- # validate_attributes(:title, :body) do |record, attribute, value|
- # record.errors[atribute] << "must no suck" if record.sucks? and value.is_silly?
- # end
- def validate_attributes(attributes, options, &proc)
- options.reverse_merge!(:on => :any)
- @validation_stack[options[:on]] << Validation.new(attributes, proc)
- end
- end
- # Instances of this class is stored in the validation stack.
- # TODO: Add support for :if and :unless (symbols for method names and procs getting the
- # instance yielded to them)
- class Validation
- def initialize(attributes, proc)
- @attributes = attributes
- @proc = proc
- end
- def run(instance)
- @attributes.each {|attribute| @proc.call(instance, attribute, instance.send(attribute)) }
- end
- end
- class Errors < Hash
- # These errors are instance-wide errors rather than attribute specific ones.
- attr_reader :base
- # We want errors[:foo] to yield an empty array. See the tests.
- def initialize
- super {|hash, key| hash[key] = [] }
- @base = []
- end
- # As we can potentially have empty arrays from calling errors[:foo], we have to
- # check if the values are all empty arrays as well.
- def empty?
- values.all?(&:empty?)
- end
- end
- end
- end
Add Comment
Please, Sign In to add comment