Skip to content

Commit

Permalink
Changed registered validators to validator factories. Extracted condi…
Browse files Browse the repository at this point in the history
…tions into classes.
  • Loading branch information
Simon Oulevay committed Dec 19, 2014
1 parent 27d314b commit 0db6ef4
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 63 deletions.
6 changes: 4 additions & 2 deletions lib/errapi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ def self.config
def self.default_config
Configuration.new.tap do |config|
config.plugins << Errapi::Plugins::ErrorCodes.new
config.register_validator :length, Errapi::Validators::Length.new
config.register_validator :presence, Errapi::Validators::Presence.new
config.register_validator :length, Errapi::Validators::Length
config.register_validator :presence, Errapi::Validators::Presence
config.register_condition Errapi::Condition::SimpleCheck
config.register_condition Errapi::Condition::ErrorCheck
end
end
end
71 changes: 71 additions & 0 deletions lib/errapi/condition.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
class Errapi::Condition
ALLOWED_CONDITIONALS = %i(if unless).freeze

def self.conditionals
h = const_get('CONDITIONALS')
raise LoadError, "The CONDITIONALS constant in class #{self} is of the wrong type (#{h.class}). Either make it a Hash or override #{self}.conditionals to return a list of symbols." unless h.kind_of? Hash
h.keys
end

def initialize conditional, predicate, options = {}

@conditional = resolve_conditional conditional
raise ArgumentError, "Conditional must be either :if or :unless" unless ALLOWED_CONDITIONALS.include? @conditional

@predicate = predicate
end

def fulfilled? *args
result = check @predicate, *args
result = !result if @conditional == :unless
result
end

def resolve_conditional conditional
conditional
end

def check predicate, value, context, options = {}
raise NotImplementedError, "Subclasses should implement the #check method to check whether the value matches the predicate of the condition"
end

class SimpleCheck < Errapi::Condition

CONDITIONALS = {
if: :if,
unless: :unless
}.freeze

def check predicate, value, context, options = {}
if @predicate.kind_of?(Symbol) || @predicate.kind_of?(String)
value.respond_to?(:[]) ? value[@predicate] : value.send(@predicate)
elsif @predicate.respond_to? :call
@predicate.call value, context, options
else
@predicate
end
end
end

class ErrorCheck < Errapi::Condition

CONDITIONALS = {
if_error: :if,
unless_error: :unless
}.freeze

def resolve_conditional conditional
CONDITIONALS[conditional]
end

def check predicate, value, context, options = {}
if @predicate.respond_to? :call
context.errors? &@predicate
elsif @predicate.kind_of? Hash
context.errors? @predicate
else
@predicate ? context.errors? : !context.errors?
end
end
end
end
27 changes: 21 additions & 6 deletions lib/errapi/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,34 @@ class Errapi::Configuration
def initialize
@plugins = []
@validators = {}
@conditions = {}
end

def new_context
Errapi::ValidationContext.new plugins: @plugins
end

def register_validator name, validator
raise ArgumentError, "The supplied object is not a validator (it does not respond to the #validate method)" unless validator.respond_to? :validate
@validators[name] = validator
def register_validator name, factory
@validators[name] = factory
end

def validator name
raise ArgumentError, "No validator found with name #{name.inspect}" unless @validators.key? name
@validators[name]
def validator name, options = {}
raise ArgumentError, "No validator factory registered for name #{name.inspect}" unless @validators.key? name
@validators[name].new options
end

def register_condition factory
factory.conditionals.each do |conditional|
@conditions[conditional] = factory
end
end

def extract_conditions! source, options = {}
[].tap do |conditions|
@conditions.each_pair do |conditional,factory|
next unless source.key? conditional
conditions << factory.new(conditional, source.delete(conditional), options)
end
end
end
end
61 changes: 10 additions & 51 deletions lib/errapi/object_validations.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
class Errapi::ObjectValidations

def initialize &block
def initialize options = {}, &block

@validations = []
@current_options = {}
@config = options[:config] || Errapi.config

instance_eval &block if block
end

Expand All @@ -23,8 +26,6 @@ def validates_each *args, &block

def validate value, context, options = {}

config = options.delete(:config) || Errapi.config

context.with self do
with_options options do

Expand Down Expand Up @@ -53,7 +54,7 @@ def validate value, context, options = {}

values.each.with_index do |value,i|

next if validation[:conditions].any?{ |condition| !evaluate_condition(condition, value, context) }
next if validation[:conditions].any?{ |condition| !condition.fulfilled?(value, context) }

iteration_options = {}
iteration_options = { location: actual_location(relative_location: i), value_set: values_set[i] } if each
Expand All @@ -71,7 +72,7 @@ def validate value, context, options = {}
value_context_options[:value] = current_value
value_context_options[:value_set] = value_data[:value_set]

validator = validation[:validator] || config.validator(validation[:validator_name])
validator = validation[:validator]
value_context_options[:validator_name] = validation[:validator_name] if validation[:validator_name]
value_context_options[:validator_options] = validation[:validator_options]

Expand Down Expand Up @@ -164,7 +165,7 @@ def register_validations *args, &block
custom_validators << options.delete(:using) if options[:using] # TODO: allow array
custom_validators << Errapi::ObjectValidations.new(&block) if block

conditions = extract_conditions! options
conditions = @config.extract_conditions! options

# TODO: fail if there are no validations declared
args = [ nil ] if args.empty?
Expand Down Expand Up @@ -197,55 +198,13 @@ def register_validations *args, &block
validation.merge!({
validator_options: validator_options,
context_options: validator_options.delete(:with) || context_options,
conditions: conditions + extract_conditions!(validator_options)
conditions: conditions + @config.extract_conditions!(validator_options)
})

@validations << validation.merge(target_options)
end
end
end

def extract_conditions! options = {}
# TODO: wrap conditions in objects that can cache the result
[].tap do |conditions|
conditions << { if: options.delete(:if) } if options[:if]
conditions << { unless: options.delete(:unless) } if options[:unless]
conditions << { if_error: options.delete(:if_error) } if options[:if_error]
conditions << { unless_error: options.delete(:unless_error) } if options[:unless_error]
end
end

def evaluate_condition condition, value, context

conditional, condition_type, predicate = if condition.key? :if
[ lambda{ |x| !!x }, :custom, condition[:if] ]
elsif condition.key? :unless
[ lambda{ |x| !x }, :custom, condition[:unless] ]
elsif condition.key? :if_error
[ lambda{ |x| !!x }, :error, condition[:if_error] ]
elsif condition.key? :unless_error
[ lambda{ |x| !x }, :error, condition[:unless_error] ]
end
validation[:validator] = @config.validator validator_name, validator_options

result = case condition_type
when :custom
if predicate.kind_of?(Symbol) || predicate.kind_of?(String)
value.respond_to?(:[]) ? value[predicate] : value.send(predicate)
elsif predicate.respond_to? :call
predicate.call value, context, @current_options
else
predicate
end
when :error
if predicate.respond_to? :call
context.errors? &predicate
elsif predicate.kind_of? Hash
context.errors? predicate
else
predicate ? context.errors? : !context.errors?
@validations << validation.merge(target_options)
end
end

conditional.call result
end
end
7 changes: 5 additions & 2 deletions lib/errapi/validators/length.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ class Errapi::Validators::Length
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
CAUSES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze

def initialize options = {}
@constraints = actual_constraints options
end

def validate value, context, options = {}
return unless value.respond_to? :length

options = actual_constraints options
actual_length = value.length

CHECKS.each_pair do |key,check|
next unless check_value = options[key]
next unless check_value = @constraints[key]
next if actual_length.send check, check_value
context.add_error cause: CAUSES[check]
end
Expand Down
9 changes: 7 additions & 2 deletions lib/errapi/validators/presence.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
class Errapi::Validators::Presence

def initialize options = {}
end

def validate value, context, options = {}
if value_blank? value
context.add_error cause: :blank
Expand All @@ -11,9 +14,11 @@ def validate value, context, options = {}
BLANK_REGEXP = /\A[[:space:]]*\z/

def value_blank? value
if value.kind_of? String
if value.respond_to? :blank?
value.blank?
elsif value.kind_of? String
BLANK_REGEXP === value
elsif value.respond_to?(:empty?)
elsif value.respond_to? :empty?
value.empty?
else
!value
Expand Down

0 comments on commit 0db6ef4

Please sign in to comment.