veto 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +561 -0
  6. data/Rakefile +1 -0
  7. data/lib/veto/attribute_validator_factory.rb +32 -0
  8. data/lib/veto/builder.rb +65 -0
  9. data/lib/veto/conditions.rb +23 -0
  10. data/lib/veto/conditions_evaluator.rb +30 -0
  11. data/lib/veto/configuration.rb +42 -0
  12. data/lib/veto/errors.rb +35 -0
  13. data/lib/veto/exceptions.rb +5 -0
  14. data/lib/veto/model.rb +37 -0
  15. data/lib/veto/validator.rb +150 -0
  16. data/lib/veto/validators/abstract_validator.rb +15 -0
  17. data/lib/veto/validators/attribute_validator.rb +22 -0
  18. data/lib/veto/validators/custom_method_validator.rb +19 -0
  19. data/lib/veto/validators/exact_length_validator.rb +15 -0
  20. data/lib/veto/validators/format_validator.rb +15 -0
  21. data/lib/veto/validators/inclusion_validator.rb +16 -0
  22. data/lib/veto/validators/integer_validator.rb +17 -0
  23. data/lib/veto/validators/length_range_validator.rb +16 -0
  24. data/lib/veto/validators/max_length_validator.rb +15 -0
  25. data/lib/veto/validators/min_length_validator.rb +15 -0
  26. data/lib/veto/validators/not_null_validator.rb +14 -0
  27. data/lib/veto/validators/numeric_validator.rb +17 -0
  28. data/lib/veto/validators/presence_validator.rb +15 -0
  29. data/lib/veto/version.rb +3 -0
  30. data/lib/veto.rb +53 -0
  31. data/spec/attribute_validator_factory_spec.rb +72 -0
  32. data/spec/builder_spec.rb +38 -0
  33. data/spec/conditions_evaluator_spec.rb +90 -0
  34. data/spec/conditions_spec.rb +16 -0
  35. data/spec/configuration/message_spec.rb +30 -0
  36. data/spec/configuration_spec.rb +6 -0
  37. data/spec/errors_spec.rb +22 -0
  38. data/spec/model_spec.rb +67 -0
  39. data/spec/spec_helper.rb +6 -0
  40. data/spec/system/validator_spec.rb +380 -0
  41. data/spec/validator_spec.rb +119 -0
  42. data/spec/validators/exact_length_validator_spec.rb +37 -0
  43. data/spec/validators/format_validator_spec.rb +32 -0
  44. data/spec/validators/inclusion_validator_spec.rb +45 -0
  45. data/spec/validators/integer_validator_spec.rb +42 -0
  46. data/spec/validators/length_range_validator_spec.rb +55 -0
  47. data/spec/validators/max_length_validator_spec.rb +32 -0
  48. data/spec/validators/min_length_validator_spec.rb +32 -0
  49. data/spec/validators/not_null_validator_spec.rb +27 -0
  50. data/spec/validators/numeric_validator_spec.rb +42 -0
  51. data/spec/validators/presence_validator_spec.rb +47 -0
  52. data/spec/veto_spec.rb +24 -0
  53. data/veto.gemspec +24 -0
  54. metadata +161 -0
@@ -0,0 +1,65 @@
1
+ require 'veto/conditions'
2
+ require 'veto/attribute_validator_factory'
3
+ require 'veto/validators/custom_method_validator'
4
+
5
+ module Veto
6
+ class Builder
7
+ attr_reader :context, :conditions_context
8
+
9
+ def initialize context, conditions_context={}, &block
10
+ @context = context
11
+ @conditions_context = conditions_context
12
+ instance_eval(&block) if block_given?
13
+ end
14
+
15
+ def with_options conditions={}, &block
16
+ mc = ::Veto::Conditions.merge(conditions_context, conditions)
17
+ ::Veto::Builder.new(context, mc, &block)
18
+ end
19
+
20
+ def validates attribute, options={}
21
+ a = attribute
22
+ c = ::Veto::Conditions.select(options)
23
+ mc = ::Veto::Conditions.merge(conditions_context, c)
24
+ o = ::Veto::Conditions.reject(options)
25
+ vs = o.map do |t, vopts|
26
+ vo = case vopts
27
+ when TrueClass
28
+ {}
29
+ when Hash
30
+ vopts
31
+ when Range, Array
32
+ { :in => vopts }
33
+ else
34
+ { :with => vopts }
35
+ end
36
+
37
+ vc = ::Veto::Conditions.select(vo)
38
+ mvc = ::Veto::Conditions.merge(mc, vc)
39
+ mvo = vo.merge(mvc)
40
+ ::Veto::AttributeValidatorFactory.new_validator(t, a, mvo)
41
+ end
42
+ validate_with vs
43
+ end
44
+
45
+ def validate *method_names
46
+ if method_names.last.is_a?(Hash)
47
+ c = method_names.last
48
+ mns = method_names[0..-2]
49
+ else
50
+ c = {}
51
+ mns = method_names
52
+ end
53
+
54
+ mc = ::Veto::Conditions.merge(conditions_context, c)
55
+ vs = mns.map do |mn|
56
+ ::Veto::CustomMethodValidator.new(mn, mc)
57
+ end
58
+ validate_with vs
59
+ end
60
+
61
+ def validate_with *args
62
+ context.validate_with *args
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
1
+ module Veto
2
+ class Conditions
3
+ CONDITION_KEYS = [:if, :unless].freeze
4
+
5
+ def self.reject hash={}
6
+ hash.reject{|k,v| CONDITION_KEYS.include?(k) }
7
+ end
8
+
9
+ def self.select hash={}
10
+ hash.select{|k,v| CONDITION_KEYS.include?(k) }
11
+ end
12
+
13
+ def self.merge dest_hash, source_hash
14
+ CONDITION_KEYS.inject({}) do |m, k|
15
+ c = []
16
+ c << dest_hash[k]
17
+ c << source_hash[k]
18
+ m[k] = c.flatten.compact
19
+ m
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Veto
2
+ class ConditionsEvaluator
3
+ def self.truthy? condition, context, entity
4
+ case condition
5
+ when String
6
+ !!entity.instance_eval(condition)
7
+ when Symbol
8
+ !!context.send(condition)
9
+ when Proc
10
+ !!condition.call(entity)
11
+ else
12
+ !!condition
13
+ end
14
+ end
15
+
16
+ def self.truthy_conditions? conditions, context, entity
17
+ return true if !conditions.key?(:if) && !conditions.key?(:unless)
18
+
19
+ [*conditions[:if]].each do |condition|
20
+ return false unless truthy?(condition, context, entity)
21
+ end
22
+
23
+ [*conditions[:unless]].each do |condition|
24
+ return false if truthy?(condition, context, entity)
25
+ end
26
+
27
+ true
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ module Veto
2
+ class Configuration
3
+ class Message
4
+ DEFAULT_MESSAGES = {
5
+ :exact_length => lambda{|exact| "is not #{exact} characters"},
6
+ :format => lambda{"is not valid"},
7
+ :inclusion => lambda{|set| "is not in set: #{set.inspect}"},
8
+ :integer => lambda{"is not a number"},
9
+ :length_range => lambda{"is too short or too long"},
10
+ :max_length => lambda{|max| "is longer than #{max} characters"},
11
+ :min_length => lambda{|min| "is shorter than #{min} characters"},
12
+ :not_null => lambda{"is not present"},
13
+ :numeric => lambda{"is not a number"},
14
+ :presence => lambda{"is not present"}
15
+ }
16
+
17
+ def get type, *args
18
+ args.compact.length > 0 ? message(type).call(*args) : message(type).call
19
+ end
20
+
21
+ def set type, proc
22
+ custom_messages[type] = proc
23
+ end
24
+
25
+ private
26
+
27
+ def custom_messages
28
+ @custom_messages ||= {}
29
+ end
30
+
31
+ def message type
32
+ custom_messages[type] || DEFAULT_MESSAGES.fetch(type)
33
+ end
34
+ end
35
+
36
+ attr_reader :message
37
+
38
+ def initialize
39
+ @message = Message.new
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ module Veto
2
+ class Errors < ::Hash
3
+ def add atr, msg, *msg_opts
4
+ fetch(atr){self[atr] = []} << msg_lookup(msg, *msg_opts)
5
+ end
6
+
7
+ def count
8
+ values.inject(0){|m, v| m + v.length}
9
+ end
10
+
11
+ def empty?
12
+ count == 0
13
+ end
14
+
15
+ def full_messages
16
+ inject([]) do |m, kv|
17
+ atr, errors = *kv
18
+ errors.each {|e| m << "#{atr} #{e}"}
19
+ m
20
+ end
21
+ end
22
+
23
+ def on(atr)
24
+ if v = fetch(atr, nil) and !v.empty?
25
+ v
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def msg_lookup msg, *msg_opts
32
+ msg.is_a?(Symbol) ? ::Veto.configuration.message.get(msg, *msg_opts) : msg
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Veto
2
+ class VetoError < StandardError; end
3
+ class InvalidEntity < VetoError; end
4
+ class ValidatorNotAssigned < VetoError; end
5
+ end
data/lib/veto/model.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'veto/exceptions'
2
+
3
+ module Veto
4
+ module Model
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ def validates_with veto_validator
11
+ @validator = veto_validator
12
+ end
13
+
14
+ def validator
15
+ @validator || raise(::Veto::ValidatorNotAssigned, 'validator not assigned')
16
+ end
17
+ end
18
+
19
+ def valid?
20
+ validator.valid?
21
+ end
22
+
23
+ def validate!
24
+ validator.validate!
25
+ end
26
+
27
+ def errors
28
+ validator.errors
29
+ end
30
+
31
+ private
32
+
33
+ def validator
34
+ @validator ||= self.class.validator.new(self)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,150 @@
1
+ require 'veto/errors'
2
+ require 'veto/exceptions'
3
+ require 'veto/builder'
4
+
5
+ module Veto
6
+ module Validator
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def with_options *args, &block
13
+ builder.with_options(*args, &block)
14
+ end
15
+
16
+ def validates *args
17
+ builder.validates(*args)
18
+ end
19
+
20
+ def validate *args
21
+ builder.validate(*args)
22
+ end
23
+
24
+ # Used to add a validator, or set of validators, to the
25
+ # validators list. Generally used by the `validates` and `validate`
26
+ # methods internally.
27
+ #
28
+ # @example
29
+ # PersonValidator.validates_with PresenceValidator.new(:first_name)
30
+ # # OR
31
+ # PersonValidator.validates_with PresenceValidator.new(:first_name), IntegerValidator.new(:age)
32
+ #
33
+ # @param validator_set [Array] A single, or list, of validator instances
34
+ def validate_with *validator_set
35
+ validators.concat validator_set.flatten
36
+ end
37
+
38
+ # Memoizes a flat list of internal validator instances that
39
+ # will perform validations on the assigned entity.
40
+ #
41
+ # @example
42
+ # PersonValidator.validators # => [
43
+ # <Veto::FormatValidator:0xXXXXXX @attribute=#<Mock:0xXXXXXX>, @options=#<Mock:0xXXXXXX>>,
44
+ # <Veto::PresenceValidator:0xXXXXXX @attribute=#<Mock:0xXXXXXX>, @options=#<Mock:0xXXXXXX>>,
45
+ # <Veto::ExactLengthValidator:0xXXXXXX @attribute=#<Mock:0xXXXXXX>, @options=#<Mock:0xXXXXXX>>]
46
+ #
47
+ # @return [Array]
48
+ def validators
49
+ @validators ||= []
50
+ end
51
+
52
+ # Returns boolean value representing the validaty of the entity
53
+ #
54
+ # @param entity [Object] the entity instance to validate.
55
+ #
56
+ # @return [Boolean]
57
+ def valid? entity
58
+ new(entity).valid?
59
+ end
60
+
61
+ # Raises exception if entity is invalid
62
+ #
63
+ # @example
64
+ # person = Person.new
65
+ # PersonValidator.validate!(person) # => Veto::InvalidEntity, ["first name is not present", "..."]
66
+ #
67
+ # @param entity [Object] the entity instance to be validated.
68
+ # @raise [Veto::InvalidEntity] if the entity is invalid
69
+ def validate! entity
70
+ new(entity).validate!
71
+ end
72
+
73
+ private
74
+
75
+ def builder
76
+ @builder ||= ::Veto::Builder.new(self)
77
+ end
78
+ end
79
+
80
+ # Initializes validator
81
+ #
82
+ # @param entity [Object] the entity instance to validate.
83
+ def initialize entity
84
+ @entity = entity
85
+ end
86
+
87
+ # Returns validating entity instance
88
+ # @return [Object]
89
+ def entity
90
+ @entity
91
+ end
92
+
93
+ # Returns errors object
94
+ #
95
+ # @return [Veto::Errors]
96
+ def errors
97
+ @errors ||= ::Veto::Errors.new
98
+ end
99
+
100
+ # Sets errors to nil.
101
+ def clear_errors
102
+ @errors = nil
103
+ end
104
+
105
+ # Returns boolean value representing the validaty of the entity
106
+ #
107
+ # @return [Boolean]
108
+ def valid?
109
+ execute
110
+ errors.empty?
111
+ end
112
+
113
+ # Raises exception if entity is invalid
114
+ #
115
+ # @example
116
+ # person = Person.new
117
+ # validator = PersonValidator.new(person)
118
+ # validator.validate! # => Veto::InvalidEntity, ["first name is not present", "..."]
119
+ #
120
+ # @raise [Veto::InvalidEntity] if the entity is invalid
121
+ def validate!
122
+ raise(::Veto::InvalidEntity, errors.full_messages) unless valid?
123
+ end
124
+
125
+ private
126
+
127
+ # Executes validation on the entity
128
+ def execute
129
+ clear_errors
130
+ run_validators
131
+ populate_entity_errors
132
+ end
133
+
134
+ # Runs each of the classes configured validators, passing the
135
+ # validator instance, entity, and errors object as arguments.
136
+ # Each validator will inspect the entity, run validations, and
137
+ # update the errors object according the the validation rules.
138
+ def run_validators
139
+ self.class.validators.each { |validator| validator.execute(self, entity, errors) }
140
+ end
141
+
142
+ # If the entity being validated has an errors accessor defined,
143
+ # assign the errors object to the entity.
144
+ def populate_entity_errors
145
+ if entity.respond_to?(:errors=)
146
+ entity.errors = errors
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,15 @@
1
+ require 'veto/conditions_evaluator'
2
+
3
+ module Veto
4
+ class AbstractValidator
5
+ def execute(context, entity, errors)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ private
10
+
11
+ def truthy_conditions?(conditions, context, entity)
12
+ ConditionsEvaluator.truthy_conditions?(conditions, context, entity)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ require 'veto/validators/abstract_validator'
2
+
3
+ module Veto
4
+ class AttributeValidator < AbstractValidator
5
+ def initialize attribute, options={}
6
+ @attribute = attribute
7
+ @options = options
8
+ end
9
+
10
+ def execute context, entity, errors
11
+ if truthy_conditions?(@options, context, entity)
12
+ value = entity.public_send(@attribute)
13
+ validate(entity, @attribute, value, errors, @options)
14
+ end
15
+ end
16
+
17
+ def validate entity, attribute, value, errors, options={}
18
+ raise NotImplementedError
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,19 @@
1
+ require 'veto/validators/abstract_validator'
2
+
3
+ module Veto
4
+ class CustomMethodValidator < AbstractValidator
5
+
6
+ attr_reader :method_name, :conditions
7
+
8
+ def initialize method_name, conditions
9
+ @method_name = method_name
10
+ @conditions = conditions
11
+ end
12
+
13
+ def execute context, entity, errors
14
+ if truthy_conditions?(@conditions, context, entity)
15
+ context.send(method_name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class ExactLengthValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ exact = options.fetch(:with)
7
+ message = options.fetch(:message, :exact_length)
8
+ on = options.fetch(:on, attribute)
9
+
10
+ if value.nil? || value.length != exact
11
+ errors.add(on, message, exact)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class FormatValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ pattern = options.fetch(:with)
7
+ message = options.fetch(:message, :format)
8
+ on = options.fetch(:on, attribute)
9
+
10
+ unless value.to_s =~ pattern
11
+ errors.add(on, message)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class InclusionValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ set = options.fetch(:in)
7
+ inclusion_method = set.respond_to?(:cover?) ? :cover? : :include?
8
+ message = options.fetch(:message, :inclusion)
9
+ on = options.fetch(:on, attribute)
10
+
11
+ unless set.send(inclusion_method, value)
12
+ errors.add(on, message, set)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class IntegerValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ message = options.fetch(:message, :integer)
7
+ on = options.fetch(:on, attribute)
8
+
9
+ begin
10
+ Kernel.Integer(value.to_s)
11
+ nil
12
+ rescue
13
+ errors.add(on, message)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class LengthRangeValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ range = options.fetch(:in)
7
+ inclusion_method = range.respond_to?(:cover?) ? :cover? : :include?
8
+ message = options.fetch(:message, :length_range)
9
+ on = options.fetch(:on, attribute)
10
+
11
+ if value.nil? || !range.send(inclusion_method, value.length)
12
+ errors.add(on, message)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class MaxLengthValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ max = options.fetch(:with)
7
+ message = options.fetch(:message, :max_length)
8
+ on = options.fetch(:on, attribute)
9
+
10
+ if value.nil? || value.length > max
11
+ errors.add(on, message, max)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class MinLengthValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ min = options.fetch(:with)
7
+ message = options.fetch(:message, :min_length)
8
+ on = options.fetch(:on, attribute)
9
+
10
+ if value.nil? || value.length < min
11
+ errors.add(on, message, min)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class NotNullValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ message = options.fetch(:message, :not_null)
7
+ on = options.fetch(:on, attribute)
8
+
9
+ if value.nil?
10
+ errors.add(on, message)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class NumericValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ message = options.fetch(:message, :numeric)
7
+ on = options.fetch(:on, attribute)
8
+
9
+ begin
10
+ Kernel.Float(value.to_s)
11
+ nil
12
+ rescue
13
+ errors.add(on, message)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'veto/validators/attribute_validator'
2
+
3
+ module Veto
4
+ class PresenceValidator < AttributeValidator
5
+ def validate entity, attribute, value, errors, options={}
6
+ msg = options.fetch(:message, :presence)
7
+ on = options.fetch(:on, attribute)
8
+ v = value.is_a?(String) ? value.gsub(/\s+/, '') : value
9
+
10
+ if v.nil? || v.respond_to?(:empty?) && v.empty?
11
+ errors.add(on, msg)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Veto
2
+ VERSION = "0.0.1"
3
+ end
data/lib/veto.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'veto/version'
2
+ require 'veto/model'
3
+ require 'veto/validator'
4
+ require 'veto/configuration'
5
+
6
+ module Veto
7
+
8
+ # Provides access to the anonymous validator extension module
9
+ #
10
+ # @example
11
+ # class PersonValidator
12
+ # include Veto.validator
13
+ # end
14
+ #
15
+ # @return [Module] the object converted into the expected format.
16
+ def self.validator
17
+ mod = Module.new
18
+ mod.define_singleton_method :included do |base|
19
+ base.send(:include, ::Veto::Validator)
20
+ end
21
+ mod
22
+ end
23
+
24
+ # Provides access to the anonymous model extension module
25
+ #
26
+ # @example
27
+ # class Person
28
+ # include Veto.model(PersonValidator)
29
+ # end
30
+ #
31
+ # @param validator [Class] the Veto validator class
32
+ # @return [Module] the object converted into the expected format.
33
+ def self.model validator
34
+ mod = Module.new
35
+ mod.define_singleton_method :included do |base|
36
+ base.send(:include, ::Veto::Model)
37
+ base.validates_with validator
38
+ end
39
+ mod
40
+ end
41
+
42
+ def self.configure
43
+ yield(configuration)
44
+ end
45
+
46
+ def self.configuration
47
+ @configuration ||= ::Veto::Configuration.new
48
+ end
49
+
50
+ def self.configuration= val
51
+ @configuration = val
52
+ end
53
+ end