veto 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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