vanguard 0.0.3

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 (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