class Errapi::ObjectValidator

Public Class Methods

new(config, options = {}) click to toggle source

TODO: remove “options” or if used, pass them to new validators instantiated in register_validations

# File lib/errapi/object_validator.rb, line 9
def initialize config, options = {}, &block
  @config = config
  @validations = []

  @config.on_configured do
    instance_eval &block if block
  end
end

Public Instance Methods

relative_location(location) click to toggle source
# File lib/errapi/object_validator.rb, line 117
def relative_location location
  RelativeLocation.new location
end
validate(value, context, options = {}) click to toggle source
# File lib/errapi/object_validator.rb, line 33
def validate value, context, options = {}
  check_config!
  # TODO: skip validation by default if previous errors at current location
  # TODO: add support for previous value and skip validation by default if value is unchanged

  return context.valid? unless @validations

  location = if options[:location]
    options[:location]
  elsif options[:location_type]
    builder = "#{options[:location_type]}_location"
    raise "Unknown location type #{options[:location_type].inspect}" unless respond_to? builder
    send builder
  else
    no_location
  end

  source = options[:source]
  context_proxy = ContextProxy.new context, self, location

  @validations.each do |validation_definition|

    each = validation_definition[:each]

    each_values = nil
    each_values_set = nil

    if each
      each_values = extract(value, each[:target], options)[:value]
      each_values_set = each_values.collect{ |v| true } if each_values.kind_of? Array
      each_values = [] unless each_values.kind_of? Array
      each_sources = each_values.collect{ |v| source }
    else
      each_values = [ value ]
      each_values_set = [ options.fetch(:value_set, true) ]
      each_sources = [ source ]
    end

    each_location = each ? location.relative(each[:options][:as] || each[:target]) : location

    each_values.each.with_index do |each_value,i|

      each_index_location = each ? each_location.relative(i) : location
      context_proxy.current_location = each_index_location

      next if validation_definition[:conditions].any?{ |condition| !condition.fulfilled?(each_value, context_proxy) }

      validation_definition[:validations].each do |validation|

        next if validation[:conditions] && validation[:conditions].any?{ |condition| !condition.fulfilled?(each_value, context_proxy) }

        validation_definition[:targets].each do |target|

          target_value_info = extract each_value, target, value_set: each_values_set[i], source: source

          validation_location = target ? each_index_location.relative(validation[:target_alias] || target) : each_index_location
          context_proxy.current_location = validation_location

          error_options = {
            value: target_value_info[:value],
            source: target_value_info[:source],
            value_set: target_value_info[:value_set],
            constraints: validation[:validation_options]
          }

          error_options[:location] = validation_location unless validation_location.kind_of? Errapi::Locations::None

          error_options[:validation] = validation[:validation_name] if validation[:validation_name]

          context_proxy.with_error_options error_options do
            validation_options = { location: validation_location, value_set: target_value_info[:value_set] }
            validation[:validation].validate target_value_info[:value], context_proxy, validation_options
          end
        end
      end
    end
  end

  # TODO: add config option to raise error by default
  raise Errapi::ValidationFailed.new(context) if options[:raise_error] && context.errors?

  context_proxy.valid?
end
validates(*args, &block) click to toggle source
# File lib/errapi/object_validator.rb, line 18
def validates *args, &block
  check_config!
  register_validations *args, &block
end
validates_each(*args, &block) click to toggle source
# File lib/errapi/object_validator.rb, line 23
def validates_each *args, &block

  options = args.last.kind_of?(Hash) ? args.pop : {}
  options[:each] = args.shift
  options[:each_options] = options.delete(:each_options) || {}
  args << options

  validates *args, &block
end

Private Instance Methods

check_config!() click to toggle source
# File lib/errapi/object_validator.rb, line 194
def check_config!
  raise "Configuration has not been done. You must call Errapi.config to complete the configuration process." unless @config.configured?
end
extract(value, target, options = {}) click to toggle source
# File lib/errapi/object_validator.rb, line 131
def extract value, target, options = {}

  source = options[:source]
  value_set = options.fetch :value_set, true

  if target.nil?
    { value: value, value_set: value_set, source: source }
  elsif target.respond_to? :call
    { value: target.call(value), value_set: value_set, source: value }
  elsif value.kind_of? Hash
    { value: value[target], value_set: value.key?(target), source: value }
  elsif value.respond_to?(target)
    { value: value.send(target), value_set: value_set, source: value }
  else
    { value_set: false, source: value }
  end
end
register_validations(*args, &block) click to toggle source
# File lib/errapi/object_validator.rb, line 149
def register_validations *args, &block
  # TODO: allow to set custom error options (e.g. reason) when registering validation

  options = args.last.kind_of?(Hash) ? args.pop : {}
  target_alias = options.delete :as

  validations_definition = {
    validations: []
  }

  # FIXME: register all validations (from :with, from block and from hash) in the order they are given
  if options[:with]
    validations_definition[:validations] += [*options.delete(:with)].collect{ |with| { validation: with, validation_options: {}, target_alias: target_alias } }
  end

  if block
    validations_definition[:validations] << { validation: self.class.new(@config, &block), validation_options: {}, target_alias: target_alias }
  end

  if options[:each]
    validations_definition[:each] = {
      target: options.delete(:each),
      options: options.delete(:each_options)
    }
  end

  validations_definition[:conditions] = @config.extract_conditions! options

  validations = options
  raise Errapi::ValidationDefinitionInvalid, "No validation was defined. Use registered validations (e.g. `presence: true`), the :with option, or a block to define validations." if validations_definition[:validations].empty? && validations.empty?

  validations.each do |validation_name,options|
    next unless options
    validation_options = options.kind_of?(Hash) ? options : {}
    validation_target_alias = validation_options.delete(:as) || target_alias
    conditions = @config.extract_conditions! validation_options
    validation = @config.validation validation_name, validation_options
    validations_definition[:validations] << { validation: validation, validation_name: validation_name, validation_options: validation_options, target_alias: validation_target_alias, conditions: conditions }
  end

  validations_definition[:targets] = args.empty? ? [ nil ] : args

  @validations << validations_definition
end