vanguard 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.devtools +56 -0
  7. data/Guardfile +18 -0
  8. data/LICENSE +21 -0
  9. data/README.md +80 -0
  10. data/Rakefile +2 -0
  11. data/TODO +3 -0
  12. data/config/flay.yml +3 -0
  13. data/config/flog.yml +2 -0
  14. data/config/mutant.yml +3 -0
  15. data/config/reek.yml +99 -0
  16. data/config/roodi.yml +26 -0
  17. data/config/yardstick.yml +2 -0
  18. data/lib/vanguard.rb +85 -0
  19. data/lib/vanguard/builder.rb +14 -0
  20. data/lib/vanguard/builder/nullary.rb +182 -0
  21. data/lib/vanguard/dsl.rb +43 -0
  22. data/lib/vanguard/dsl/evaluator.rb +197 -0
  23. data/lib/vanguard/evaluator.rb +72 -0
  24. data/lib/vanguard/instance_methods.rb +76 -0
  25. data/lib/vanguard/matcher.rb +19 -0
  26. data/lib/vanguard/matcher/binary.rb +59 -0
  27. data/lib/vanguard/matcher/binary/and.rb +20 -0
  28. data/lib/vanguard/matcher/binary/or.rb +20 -0
  29. data/lib/vanguard/matcher/binary/xor.rb +22 -0
  30. data/lib/vanguard/matcher/nullary.rb +27 -0
  31. data/lib/vanguard/matcher/nullary/equality.rb +46 -0
  32. data/lib/vanguard/matcher/nullary/format.rb +126 -0
  33. data/lib/vanguard/matcher/nullary/greater_than.rb +45 -0
  34. data/lib/vanguard/matcher/nullary/identity.rb +50 -0
  35. data/lib/vanguard/matcher/nullary/inclusion.rb +67 -0
  36. data/lib/vanguard/matcher/nullary/less_than.rb +48 -0
  37. data/lib/vanguard/matcher/nullary/primitive.rb +47 -0
  38. data/lib/vanguard/matcher/nullary/proc.rb +44 -0
  39. data/lib/vanguard/matcher/nullary/value.rb +32 -0
  40. data/lib/vanguard/matcher/unary.rb +45 -0
  41. data/lib/vanguard/matcher/unary/attribute.rb +49 -0
  42. data/lib/vanguard/matcher/unary/not.rb +25 -0
  43. data/lib/vanguard/result.rb +93 -0
  44. data/lib/vanguard/rule.rb +43 -0
  45. data/lib/vanguard/rule/guard.rb +103 -0
  46. data/lib/vanguard/rule/nullary.rb +81 -0
  47. data/lib/vanguard/rule/nullary/attribute.rb +69 -0
  48. data/lib/vanguard/rule/nullary/attribute/absence.rb +19 -0
  49. data/lib/vanguard/rule/nullary/attribute/format.rb +46 -0
  50. data/lib/vanguard/rule/nullary/attribute/inclusion.rb +62 -0
  51. data/lib/vanguard/rule/nullary/attribute/length.rb +184 -0
  52. data/lib/vanguard/rule/nullary/attribute/predicate.rb +10 -0
  53. data/lib/vanguard/rule/nullary/attribute/presence.rb +32 -0
  54. data/lib/vanguard/rule/nullary/attribute/presence/not_blank.rb +0 -0
  55. data/lib/vanguard/rule/nullary/attribute/presence/not_nil.rb +0 -0
  56. data/lib/vanguard/rule/nullary/attribute/primitive.rb +35 -0
  57. data/lib/vanguard/rule/nullary/confirmation.rb +210 -0
  58. data/lib/vanguard/support/blank.rb +29 -0
  59. data/lib/vanguard/validator.rb +116 -0
  60. data/lib/vanguard/validator/builder.rb +52 -0
  61. data/lib/vanguard/violation.rb +84 -0
  62. data/spec/integration/vanguard/dsl/guard_spec.rb +58 -0
  63. data/spec/integration/vanguard/dsl/validates_absence_of_spec.rb +19 -0
  64. data/spec/integration/vanguard/dsl/validates_acceptance_of_spec.rb +19 -0
  65. data/spec/integration/vanguard/dsl/validates_confirmation_of_spec.rb +27 -0
  66. data/spec/integration/vanguard/dsl/validates_format_of_spec.rb +79 -0
  67. data/spec/integration/vanguard/dsl/validates_inclusion_of_spec.rb +23 -0
  68. data/spec/integration/vanguard/dsl/validates_length_of_spec.rb +77 -0
  69. data/spec/integration/vanguard/dsl/validates_presence_of_spec.rb +19 -0
  70. data/spec/integration/vanguard/dsl/validates_value_of_spec.rb +30 -0
  71. data/spec/integration/vanguard/validator_spec.rb +57 -0
  72. data/spec/rcov.opts +6 -0
  73. data/spec/shared/dsl_spec.rb +73 -0
  74. data/spec/spec_helper.rb +3 -0
  75. data/spec/suite.rb +10 -0
  76. data/spec/unit/vanguard/support/blank_spec.rb +72 -0
  77. data/vanguard.gemspec +23 -0
  78. metadata +190 -0
@@ -0,0 +1,14 @@
1
+ module Vanguard
2
+ # Abstract base class for builders
3
+ class Builder
4
+ include Adamantium::Flat, AbstractType
5
+
6
+ # Return rules
7
+ #
8
+ # @return [Enumerable<Rule>]
9
+ #
10
+ # @api private
11
+ #
12
+ abstract_method :rules
13
+ end
14
+ end
@@ -0,0 +1,182 @@
1
+ module Vanguard
2
+ class Builder
3
+ # Abstract base class for nullary builders
4
+ class Nullary < self
5
+ include AbstractType
6
+
7
+ OPTIONS = [].freeze
8
+ REQUIRED_OPTIONS = [].freeze
9
+
10
+ # Return options
11
+ #
12
+ # @return [Hash]
13
+ #
14
+ # @api private
15
+ #
16
+ attr_reader :options
17
+
18
+ # Return arguments
19
+ #
20
+ # @return [Class]
21
+ #
22
+ # @api private
23
+ #
24
+ attr_reader :klass
25
+
26
+ # Return arguments
27
+ #
28
+ # @return [Enumerable<Object>]
29
+ #
30
+ # @api private
31
+ #
32
+ attr_reader :arguments
33
+
34
+ # Return rule for attribute name
35
+ #
36
+ # @param [Symbol] attribute_name
37
+ #
38
+ # @return [Rule]
39
+ #
40
+ # @api private
41
+ #
42
+ def rule(attribute_name)
43
+ klass.new(attribute_name, matcher)
44
+ end
45
+
46
+ # Return matcher
47
+ #
48
+ # @return [Matcher]
49
+ #
50
+ # @api private
51
+ #
52
+ def matcher
53
+ klass::MATCHER
54
+ end
55
+
56
+ # Return rules
57
+ #
58
+ # @api private
59
+ #
60
+ def rules
61
+ arguments.map do |attribute_name|
62
+ rule(attribute_name)
63
+ end
64
+ end
65
+ memoize :rules
66
+
67
+ # Return allowed options
68
+ #
69
+ # @return [Enumerable<Symbol>]
70
+ #
71
+ # @api private
72
+ #
73
+ def allowed_options
74
+ self.class::OPTIONS
75
+ end
76
+
77
+ # Return required options
78
+ #
79
+ # @return [Enumerable<Symbol>]
80
+ #
81
+ # @api private
82
+ #
83
+ def required_options
84
+ self.class::REQUIRED_OPTIONS
85
+ end
86
+
87
+ # Run builder
88
+ #
89
+ # @return [Enumerable<Rule>]
90
+ #
91
+ # @api private
92
+ #
93
+ def self.run(*arguments)
94
+ new(*arguments).rules
95
+ end
96
+
97
+ private
98
+
99
+ # Initialize object
100
+ #
101
+ # @param [Class] klass
102
+ # @param [Enumerable<Object>] arguments
103
+ # @param [Hash] options
104
+ #
105
+ # @api private
106
+ #
107
+ def initialize(klass, arguments, options)
108
+ @klass, @arguments, @options = klass, arguments, options
109
+ assert_known_options
110
+ assert_required_options
111
+ assert_arguments
112
+ end
113
+
114
+ # Assert arguments are present
115
+ #
116
+ # TODO: Raise more specific expection
117
+ #
118
+ # @return [undefined]
119
+ #
120
+ # @raise [RuntimeError]
121
+ # if arguments are empty
122
+ #
123
+ # @return [undefined]
124
+ #
125
+ # @api private
126
+ #
127
+ def assert_arguments
128
+ if arguments.empty?
129
+ raise "#{klass} needs at least one attribute name"
130
+ end
131
+ end
132
+
133
+ # Assert no unknown options are present
134
+ #
135
+ # TODO: Raise more specific exception
136
+ #
137
+ # @raise [RuntimeError]
138
+ # if unknown options are present
139
+ #
140
+ # @return [undefined]
141
+ #
142
+ # @api private
143
+ #
144
+ def assert_known_options
145
+ unknown = present_keys - (allowed_options + required_options)
146
+
147
+ unless unknown.empty?
148
+ raise "Unknown options: #{unknown.inspect}"
149
+ end
150
+ end
151
+
152
+ # Return present option keys
153
+ #
154
+ # @return [Enumerable<Object>]
155
+ #
156
+ # @api private
157
+ #
158
+ def present_keys
159
+ options.keys
160
+ end
161
+
162
+ # Assert required options are present
163
+ #
164
+ # TODO: Raise more specific exception
165
+ #
166
+ # @raise [RuntimeException]
167
+ # if required options are not present
168
+ #
169
+ # @return [undefined]
170
+ #
171
+ # @api private
172
+ #
173
+ def assert_required_options
174
+ missing = required_options - present_keys
175
+
176
+ unless missing.empty?
177
+ raise "Missing options: #{missing.inspect}"
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Vanguard
4
+ # Mixin for the Vanguard dsl
5
+ module DSL
6
+
7
+ REGISTRY = {}
8
+
9
+ # Register macro
10
+ #
11
+ # @param [Symbol] name
12
+ #
13
+ # @param [Class] klass
14
+ #
15
+ # @return [self]
16
+ #
17
+ # @api private
18
+ #
19
+ def self.register(name, klass)
20
+ REGISTRY[name] = klass
21
+ self
22
+ end
23
+
24
+ # Hook called when method is missing
25
+ #
26
+ # @param [Symbol] method_name
27
+ #
28
+ # @return [self]
29
+ #
30
+ # @api private
31
+ #
32
+ def method_missing(method_name, *arguments)
33
+ klass = REGISTRY.fetch(method_name) { super }
34
+
35
+ Evaluator.new(klass, arguments).rules.each do |rule|
36
+ add(rule)
37
+ end
38
+
39
+ self
40
+ end
41
+
42
+ end # module Macros
43
+ end # module Vanguard
@@ -0,0 +1,197 @@
1
+ module Vanguard
2
+ module DSL
3
+ class Evaluator
4
+ include Adamantium::Flat
5
+
6
+ # Return klass
7
+ #
8
+ # @return [Class]
9
+ #
10
+ # @api private
11
+ #
12
+ attr_reader :klass
13
+
14
+ # Return options hash
15
+ #
16
+ # @return [Hash]
17
+ #
18
+ # @api private
19
+ #
20
+ attr_reader :options
21
+
22
+ # Return arguments array
23
+ #
24
+ # @return [Array]
25
+ #
26
+ # @api private
27
+ #
28
+ attr_reader :arguments
29
+
30
+ # Return rules
31
+ #
32
+ # @return [Enumerable<Rule>]
33
+ #
34
+ # @api private
35
+ #
36
+ def rules
37
+ klass.run(arguments, rule_options).map do |rule|
38
+ connector.connect(rule)
39
+ end
40
+ end
41
+ memoize :rules
42
+
43
+ private
44
+
45
+ # Initialize object
46
+ #
47
+ # @param [Class] klass
48
+ # @param [Enumerable<Object>] arguments
49
+ #
50
+ # @return [undefined]
51
+ #
52
+ # @api private
53
+ #
54
+ def initialize(klass, arguments)
55
+ @klass = klass
56
+ @options = arguments.last.kind_of?(Hash) ? arguments.pop : {}
57
+ @arguments = arguments
58
+ assert_no_conflict
59
+ end
60
+
61
+ # Return connector
62
+ #
63
+ # @return [Connector]
64
+ #
65
+ # @api private
66
+ #
67
+ def connector
68
+ if_connector || unless_connector || Connector::NONE
69
+ end
70
+ memoize :connector
71
+
72
+ class Connector
73
+ include Adamantium::Flat, Equalizer.new(:klass, :left)
74
+
75
+ # Return klass
76
+ #
77
+ # @return [Class]
78
+ #
79
+ # @api private
80
+ #
81
+ attr_reader :klass
82
+
83
+ # Return left rule
84
+ #
85
+ # @return [Rule]
86
+ #
87
+ # @api private
88
+ #
89
+ attr_reader :left
90
+
91
+ # Return connected rule
92
+ #
93
+ # @return [Rule] right
94
+ #
95
+ # @api private
96
+ #
97
+ def connect(right)
98
+ @klass.new(left, right)
99
+ end
100
+
101
+ Vanguard.singleton_constant(self, :NONE) do
102
+
103
+ # Return connector
104
+ #
105
+ # @param [Rule] right
106
+ #
107
+ # @return [Rule]
108
+ #
109
+ # @api private
110
+ #
111
+ def connect(right)
112
+ right
113
+ end
114
+
115
+ private
116
+
117
+ # Initialize object
118
+ #
119
+ # @return [undefined]
120
+ #
121
+ # @api private
122
+ #
123
+ def initialize; end
124
+
125
+ end
126
+
127
+ private
128
+
129
+ # Initialize object
130
+ #
131
+ # @param [Class] klass
132
+ # @param [Rule] left
133
+ #
134
+ # @return [undefined]
135
+ #
136
+ # @api private
137
+ #
138
+ def initialize(klass, left)
139
+ @klass, @left = klass, left
140
+ end
141
+ end
142
+
143
+ # Return if connector
144
+ #
145
+ # @return [Connector]
146
+ # if :if option is present
147
+ #
148
+ # @api private
149
+ #
150
+ def if_connector
151
+ name = options.fetch(:if) { return }
152
+ predicate = Matcher::Unary::Attribute.new(name, Matcher::Nullary::Identity::TRUE)
153
+ Connector.new(Rule::Guard, predicate)
154
+ end
155
+
156
+ # Return unless connector
157
+ #
158
+ # @return [Connector]
159
+ # if :unless option is present
160
+ #
161
+ # @api private
162
+ #
163
+ def unless_connector
164
+ name = options.fetch(:unless) { return }
165
+ predicate = Matcher::Unary::Attribute.new(name, Matcher::Nullary::Identity::TRUE)
166
+ predicate = Matcher::Unary::NOT.new(perdicate)
167
+ Connector.new(Rule::Guard, rule)
168
+ end
169
+
170
+ # Assert no conflicting connectors are present
171
+ #
172
+ # @return [undefined]
173
+ #
174
+ # @api private
175
+ #
176
+ def assert_no_conflict
177
+ if if_connector and unless_connector
178
+ raise "Cannot specify both :if and :unless options"
179
+ end
180
+ end
181
+
182
+ # Return rule options
183
+ #
184
+ # @return [Hash]
185
+ #
186
+ # @api private
187
+ #
188
+ def rule_options
189
+ options = self.options.dup
190
+ options.delete(:if)
191
+ options.delete(:unless)
192
+ options
193
+ end
194
+ memoize :rule_options
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,72 @@
1
+ module Vanguard
2
+ # Abstract base class for rule evaluators
3
+ class Evaluator
4
+ include Adamantium::Flat, AbstractType, Equalizer.new(:rule, :resource, :valid?)
5
+
6
+ # Return validated resource
7
+ #
8
+ # @return [Object]
9
+ #
10
+ # @api private
11
+ #
12
+ attr_reader :resource
13
+
14
+ # Return rule
15
+ #
16
+ # @return [Rule]
17
+ #
18
+ # @api private
19
+ #
20
+ attr_reader :rule
21
+
22
+ # Initialize object
23
+ #
24
+ # @param [Rule] rule
25
+ # @param [Object] resource
26
+ #
27
+ # @return [undefined]
28
+ #
29
+ # @api private
30
+ #
31
+ def initialize(rule, resource)
32
+ @rule, @resource = rule, resource
33
+ end
34
+
35
+ # Return value to be validated
36
+ #
37
+ # @return [Object]
38
+ #
39
+ # @api private
40
+ #
41
+ def value
42
+ resource.public_send(rule.attribute_name)
43
+ end
44
+ memoize :value
45
+
46
+ # Return violation
47
+ #
48
+ # @return [Violation]
49
+ #
50
+ # @api private
51
+ #
52
+ def violation
53
+ Violation.new(rule, resource)
54
+ end
55
+ memoize :violation
56
+
57
+ # Return violations
58
+ #
59
+ # @return [Enumerable<Violation>]
60
+ #
61
+ # @api private
62
+ #
63
+ def violations
64
+ unless valid?
65
+ [violation]
66
+ else
67
+ []
68
+ end
69
+ end
70
+ memoize :violations
71
+ end
72
+ end