lib/dao/validations/validator.rb in dao-4.2.1 vs lib/dao/validations/validator.rb in dao-4.4.2
- old
+ new
@@ -1,170 +1,275 @@
+# -*- encoding : utf-8 -*-
module Dao
module Validations
class Validator
- NotBlank = lambda{|value| !value.to_s.strip.empty?} unless defined?(NotBlank)
+ NotBlank = proc{|value| !value.to_s.strip.empty?} unless defined?(NotBlank)
Cleared = 'Cleared'.freeze unless defined?(Cleared)
+ include Common
+
+ def validator
+ self
+ end
+
+ def validator=(validator)
+ raise NotImplementedError
+ end
+
class << Validator
- def for(*args, &block)
- new(*args, &block)
+ def mixin(*args, &block)
+ new(*args, &block).tap{|validator| validator.mixin = true}
end
end
attr_accessor :object
+ attr_accessor :options
attr_accessor :validations
attr_accessor :errors
attr_accessor :status
- def initialize(object)
- @object = object
- @validations = Map.new
- @errors = Errors.new
- @status = Status.new
+ fattr(:attributes){ extract_attributes! }
+ alias_method(:data, :attributes)
+
+ fattr(:mixin){ false }
+
+ def initialize(*args, &block)
+ @object = args.shift
+ @options = Map.options_for(args)
+
+ if args.size == 1 and @object and @object.is_a?(Hash)
+ object_keys = @object.keys.map{|key| key.to_s}
+ option_keys = %w( object validations errors status )
+
+ object_is_options = !object_keys.empty? && (object_keys - option_keys).empty?
+
+ if object_is_options
+ @options = Map.for(@object)
+ @object = nil
+ end
+ end
+
+ @object ||= (@options[:object] || Map.new)
+ @validations ||= (@options[:validations] || Map.new)
+ @errors ||= (@options[:errors] || Errors.new)
+ @status ||= (@options[:status] || Status.new)
+
+ unless @object.respond_to?(:validator)
+ @object.send(:extend, Dao::Validations)
+ @object.validator = self
+ end
+
+ #@object.extend(InstanceExec) unless @object.respond_to?(:instance_exec)
end
- fattr(:attributes) do
+ def extract_attributes!(object = @object)
attributes =
catch(:attributes) do
- if @object.respond_to?(:attributes)
- throw :attributes, @object.attributes
+ if object.respond_to?(:attributes)
+ throw :attributes, object.attributes
end
- if @object.instance_variable_defined?('@attributes')
- throw :attributes, @object.instance_variable_get('@attributes')
+ if object.instance_variable_defined?('@attributes')
+ throw :attributes, object.instance_variable_get('@attributes')
end
- if @object.is_a?(Map)
- throw :attributes, @object
+ if object.is_a?(Map)
+ throw :attributes, object
end
- if @object.respond_to?(:to_map)
- throw :attributes, Map.new(@object.to_map)
+ if object.respond_to?(:to_map)
+ throw :attributes, Map.new(object.to_map)
end
- if @object.is_a?(Hash)
- throw :attributes, Map.new(@object)
+ if object.is_a?(Hash)
+ throw :attributes, Map.new(object)
end
- if @object.respond_to?(:to_hash)
- throw :attributes, Map.new(@object.to_hash)
+ if object.respond_to?(:to_hash)
+ throw :attributes, Map.new(object.to_hash)
end
- raise ArgumentError.new("found no attributes on #{ @object.inspect }(#{ @object.class.name })")
+ raise ArgumentError.new("found no attributes on #{ object.inspect }(#{ object.class.name })")
end
- case attributes
- when Map
- attributes
- when Hash
- Map.new(attributes)
- else
- raise(ArgumentError.new("#{ attributes.inspect } (#{ attributes.class })"))
- end
+ @attributes =
+ case attributes
+ when Map
+ attributes
+ when Hash
+ Map.new(attributes)
+ else
+ raise(ArgumentError.new("#{ attributes.inspect } (#{ attributes.class })"))
+ end
+
+ @attributes
end
- def add(*args, &block)
- options = Map.options_for!(args)
+ def validates(*args, &block)
block = args.pop if args.last.respond_to?(:call)
block ||= NotBlank
callback = Callback.new(options, &block)
- validations.set(args => Callback::Chain.new) unless validations.has?(args)
- validations.get(args).add(callback)
+ options = Map.options_for!(args)
+ key = key_for(args)
+ validations = stack.validations.last || self.validations
+ validations[key] ||= Callback::Chain.new
+ validations[key].add(callback)
callback
end
- alias_method('validates', 'add')
+ alias_method('add', 'validates')
- def run_validations!(*args)
- run_validations(*args)
- ensure
- validated!
+ def validates_each(*args, &block)
+ options = Map.options_for!(args)
+ key = key_for(args)
+
+ args.push(options)
+
+ validates(*args) do |list|
+ Array(list).each_with_index do |item, index|
+ args = Dao.args_for_arity([item], block.arity)
+ validates(index, &block)
+ end
+ true
+ end
end
+ def stack
+ @stack ||= Map[:validations, [], :prefixes, []]
+ end
+
+ def prefixing(*prefix, &block)
+ prefix = Array(prefix).flatten.compact
+ push_prefix(prefix)
+ begin
+ block.call(*[prefix].slice(0, block.arity))
+ ensure
+ pop_prefix
+ end
+ end
+ alias_method('validating', 'prefixing')
+
+ def push_prefix(prefix)
+ prefix = Array(prefix).flatten.compact
+ stack.prefixes.push(prefix)
+ end
+
+ def pop_prefix
+ stack.prefixes.pop
+ end
+
+ def prefix
+ stack.prefixes.flatten.compact
+ end
+
+ def key_for(*key)
+ prefix + Array(key).flatten.compact
+ end
+
+ def get(key)
+ attributes.get(key_for(key))
+ end
+
+ def set(key, val)
+ attributes.set(key_for(key), val)
+ end
+
+ def has(key)
+ attributes.has(key_for(key))
+ end
+
+ alias_method 'has?', 'has'
+
def validations_search_path
@validations_search_path ||= (
- list = [
- object,
- object.class.ancestors.map{|ancestor| ancestor.respond_to?(:validator) ? ancestor : nil}
- ]
- list.flatten!
- list.compact!
- list.reverse!
- list
+ if mixin?
+ list = [
+ object.respond_to?(:validator) ? object : nil,
+ object.class.ancestors.map{|ancestor| ancestor.respond_to?(:validator) ? ancestor : nil}
+ ]
+ list.flatten!
+ list.compact!
+ list.reverse!
+ list.uniq!
+ list
+ else
+ [self]
+ end
)
end
def validations_list
- validations_search_path.map{|object| object.validator.validations}
+ validations_search_path.map{|object| object.validator.validations}.uniq
end
- def run_validations(*args)
- object = args.first || @object
+ def run_validations(list = validations_list)
+ loop do
+ stack.validations.push(Map.new)
- attributes.extend(InstanceExec) unless attributes.respond_to?(:instance_exec)
+ _run_validations(errors, list)
- previous_errors = []
- new_errors = []
+ added = stack.validations.pop
+ break if added.empty?
+ list = [added]
+ end
- errors.each_message do |keys, message|
- previous_errors.push([keys, message])
+ if status.ok? and !errors.empty?
+ status.source = errors
+ status.update(412)
end
- errors.clear!
- status.ok!
- list = validations_list
+ if status == 412 and status.source == errors and errors.empty?
+ status.update(200)
+ end
- list.each do |validations|
- validations.depth_first_each do |keys, chain|
+ errors
+ ensure
+ validated!(true)
+ end
+
+ alias_method 'run_validations!', 'run_validations'
+ alias_method 'validate', 'run_validations'
+
+ def _run_validations(errors, list)
+ Array(list).each do |validations|
+ validations.each do |keys, chain|
chain.each do |callback|
next unless callback and callback.respond_to?(:to_proc)
number_of_errors = errors.size
value = attributes.get(keys)
returned =
catch(:validation) do
- args = [value, attributes].slice(0, callback.arity)
- attributes.instance_exec(*args, &callback)
+ args = Dao.args_for_arity([value, attributes], callback.arity)
+
+ prefixing(keys) do
+ object.instance_exec(*args, &callback)
+ end
end
+ errors_added = errors.size > number_of_errors
+
case returned
when Hash
- map = Map(returned)
+ map = Map.for(returned)
valid = map[:valid]
message = map[:message]
-
when TrueClass, FalseClass
valid = returned
message = nil
-
else
- any_errors_added = errors.size > number_of_errors
- valid = !any_errors_added
+ valid = !errors_added
message = nil
end
+ valid = false if errors_added
+
message ||= callback.options[:message]
message ||= (value.to_s.strip.empty? ? 'is blank' : 'is invalid')
- unless valid
- new_errors.push([keys, message])
+ if not valid
+ errors.add_from_source(keys, callback, message)
else
- new_errors.push([keys, Cleared])
+ errors.delete_from_source(keys, callback)
end
end
end
end
-
- previous_errors.each do |keys, message|
- errors.add(keys, message) unless new_errors.assoc(keys)
- end
-
- new_errors.each do |keys, value|
- next if value == Cleared
- message = value
- errors.add(keys, message)
- end
-
- if status.ok? and !errors.empty?
- status.update(412)
- end
-
- errors
end
def validated?
@validated = false unless defined?(@validated)
@validated
@@ -172,14 +277,10 @@
def validated!(boolean = true)
@validated = !!boolean
end
- def validate
- run_validations
- end
-
def validate!
raise Error.new("#{ object.class.name } is invalid!") unless valid?
object
end
@@ -198,10 +299,10 @@
def valid?(*args)
if forcing_validity?
true
else
options = Map.options_for!(args)
- validate #if(options[:validate] or !validated?)
+ run_validations
errors.empty? and status.ok?
end
end
def reset