ratatouille 0.1.0
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.
- data/.gitignore +18 -0
- data/.rvmrc +1 -0
- data/.travis-yml +8 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +249 -0
- data/Rakefile +12 -0
- data/lib/ratatouille/array.rb +120 -0
- data/lib/ratatouille/hash.rb +108 -0
- data/lib/ratatouille/nilclass.rb +6 -0
- data/lib/ratatouille/ratifier.rb +98 -0
- data/lib/ratatouille/string.rb +6 -0
- data/lib/ratatouille/version.rb +4 -0
- data/lib/ratatouille.rb +18 -0
- data/ratatouille.gemspec +22 -0
- data/spec/lib/ratatouille/array_spec.rb +111 -0
- data/spec/lib/ratatouille/hash_spec.rb +51 -0
- data/spec/lib/ratatouille/nilclass_spec.rb +9 -0
- data/spec/lib/ratatouille/ratifier_spec.rb +123 -0
- data/spec/lib/ratatouille_spec.rb +19 -0
- data/spec/spec_helper.rb +9 -0
- metadata +122 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use 1.8.7@ratatouille
|
data/.travis-yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
'lib/**/*.rb' - '*.md'
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Ryan Johnson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
# Ratatouille [](http://travis-ci.org/CITguy/ratatouille)
|
2
|
+
|
3
|
+
DSL for validation of complex Hashes
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ratatouille'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ratatouille
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
## Blocks
|
22
|
+
|
23
|
+
All of the given methods accept a block for validation and will not progress into the block if the core method logic does not validate.
|
24
|
+
However, some methods may be used without a block and validation will progress whether or not the method logic validates.
|
25
|
+
|
26
|
+
|
27
|
+
### ratifiable\_object
|
28
|
+
|
29
|
+
Within a block, the ratifiable\_object method provides the object that is to be validated against.
|
30
|
+
This will change when using *given\_key*.
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
All of the following methods perform validation on the *ratifiable\_object* defined in scope of the block.
|
37
|
+
|
38
|
+
|
39
|
+
### given\_key
|
40
|
+
|
41
|
+
This method is used to scope its given block to the key value. Useful to reduce the need to explicitly namespace nested keys in *ratifiable\_object*.
|
42
|
+
|
43
|
+
* **This method doesn't perform any validation and must be used with a block to get any use out of it.**
|
44
|
+
* Changes *ratifiable\_object* to *key value* (original scope will return on block exit)
|
45
|
+
|
46
|
+
|
47
|
+
#### Syntax
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
given_key(:foo) do
|
51
|
+
# validation for ratifiable_object[:foo]
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
#### Example
|
56
|
+
|
57
|
+
See **choice\_of** for an example.
|
58
|
+
|
59
|
+
|
60
|
+
### choice\_of
|
61
|
+
|
62
|
+
Meant to be used to validate that X number of given key choices exist in the Hash.
|
63
|
+
|
64
|
+
* Number of Choices must be less than size of key choice array
|
65
|
+
* Can be used without a block
|
66
|
+
* Works well with **given\_key()**.
|
67
|
+
|
68
|
+
|
69
|
+
#### Syntax
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
choice_of(number_of_choices, key_choice_array) do
|
73
|
+
# Validation provided number of choices is satisfied
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
|
78
|
+
#### Example
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
r = ratify({:foo => "bar", :bar => "biz"}) do
|
82
|
+
choice_of(1, :foo, :bar) do
|
83
|
+
# Validation, provided :foo OR :bar exists
|
84
|
+
end
|
85
|
+
end
|
86
|
+
r.valid? #=> false (:foo OR :bar must be defined, NOT BOTH)
|
87
|
+
|
88
|
+
r = ratify({:foo => "bar", :bar => "biz"}) do
|
89
|
+
choice_of(2, :foo, :bar, :biz) do
|
90
|
+
# Validation, provided 2 of the following exist: :foo, :bar, :biz
|
91
|
+
|
92
|
+
given_key(:foo) do
|
93
|
+
# in context of ratifiable_object[:foo]
|
94
|
+
end
|
95
|
+
|
96
|
+
given_key(:bar) do
|
97
|
+
# in context of ratifiable_object[:bar]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
r.valid? #=> true (a choice of 2 items from [:foo, :bar, :biz] is satisfied)
|
102
|
+
|
103
|
+
r = ratify({:foo => "bar", :bar => "biz"}) do
|
104
|
+
choice_of(2, :foo, :bar) do
|
105
|
+
# Validation ...
|
106
|
+
end
|
107
|
+
end
|
108
|
+
r.valid? #=> false (you might as well use required_keys)
|
109
|
+
```
|
110
|
+
|
111
|
+
|
112
|
+
### required\_keys
|
113
|
+
|
114
|
+
Used to ensure that the list of keys exist in the Hash.
|
115
|
+
|
116
|
+
* Block is optional
|
117
|
+
|
118
|
+
|
119
|
+
#### Syntax
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
# Validate that the keys exist and perform validation if they do
|
123
|
+
required_keys(:foo, :bar) do
|
124
|
+
# Validation provided that :foo and :bar exist
|
125
|
+
end
|
126
|
+
|
127
|
+
# Validate that the keys exist
|
128
|
+
required_keys(:foo, :bar)
|
129
|
+
```
|
130
|
+
|
131
|
+
|
132
|
+
#### Example
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
r = ratify({:foo => "bar", :bar => "biz"}) do
|
136
|
+
required_keys(:foo, :bar)
|
137
|
+
end
|
138
|
+
r.valid? #=> true
|
139
|
+
|
140
|
+
r = ratify({:foo => "bar"}) do
|
141
|
+
required_keys(:foo, :bar)
|
142
|
+
end
|
143
|
+
r.valid? #=> false
|
144
|
+
```
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
### is\_empty
|
149
|
+
|
150
|
+
* Self-explanatory
|
151
|
+
* Block is optional
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
r = ratify({:foo => "bar"}) do
|
155
|
+
is_empty # validation continues
|
156
|
+
is_empty do
|
157
|
+
# validation in block is never performed
|
158
|
+
end
|
159
|
+
end
|
160
|
+
r.valid? #=> false
|
161
|
+
|
162
|
+
r = ratify({}) do
|
163
|
+
is_empty # validation continues even if not empty
|
164
|
+
is_empty do
|
165
|
+
# validation continues only if ratifiable_object is empty
|
166
|
+
end
|
167
|
+
end
|
168
|
+
r.valid? #=> true
|
169
|
+
```
|
170
|
+
|
171
|
+
|
172
|
+
### is\_not\_empty
|
173
|
+
|
174
|
+
* Self-explanatory
|
175
|
+
* Block is optional
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
r = ratify({:foo => "bar"}) do
|
179
|
+
is_not_empty # validation continues even if empty
|
180
|
+
is_not_empty do
|
181
|
+
# validation continues unless ratifiable_object is empty
|
182
|
+
end
|
183
|
+
end
|
184
|
+
r.valid? #=> true
|
185
|
+
|
186
|
+
r = ratify({}) do
|
187
|
+
is_not_empty # validation continues
|
188
|
+
is_not_empty do
|
189
|
+
# validation in block is never performed
|
190
|
+
end
|
191
|
+
end
|
192
|
+
r.valid? #=> false
|
193
|
+
```
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
## Custom Validation
|
198
|
+
|
199
|
+
**Return to this section after reading the remaining sections.**
|
200
|
+
|
201
|
+
Custom validation can take place using the following methods to generate custom validation logic that cannot be satisfied with the existing methods.
|
202
|
+
|
203
|
+
You should use the **validation\_error** method to add your own errors to the Ratifier object.
|
204
|
+
|
205
|
+
|
206
|
+
### validation\_error
|
207
|
+
|
208
|
+
Used to insert validation error message into the Ratifier object.
|
209
|
+
|
210
|
+
|
211
|
+
#### Syntax
|
212
|
+
|
213
|
+
It is also possible to set the context of an error by passing in a second argument. However, it defaults to the root of the current ratifiable\_object ('/').
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
validation_error("This is an error")
|
217
|
+
validation_error("This is an error", "current_context")
|
218
|
+
```
|
219
|
+
|
220
|
+
|
221
|
+
|
222
|
+
## Advanced Example
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
include Ratatouille
|
226
|
+
r = ratify({:foo => {:bar => {:biz => "bang"}}}) do
|
227
|
+
is_not_empty
|
228
|
+
given_key(:foo) do
|
229
|
+
validation_error(":foo error")
|
230
|
+
given_key(:bar) do
|
231
|
+
validation_error(":bar error")
|
232
|
+
given_key(:biz) do
|
233
|
+
if ratifiable_object == "bang"
|
234
|
+
validation_error("should be 'shoot'")
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
r.valid? # => false
|
241
|
+
```
|
242
|
+
|
243
|
+
## Contributing
|
244
|
+
|
245
|
+
1. Fork it
|
246
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
247
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
248
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
249
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module Ratatouille
|
2
|
+
|
3
|
+
# Module used to provide Array-specific validation methods
|
4
|
+
module ArrayMethods
|
5
|
+
# @return [void]
|
6
|
+
def is_empty(&block)
|
7
|
+
unless @ratifiable_object.empty?
|
8
|
+
validation_error("Array is not empty")
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
instance_eval(&block) if block_given?
|
13
|
+
end#is_empty
|
14
|
+
|
15
|
+
|
16
|
+
# @return [void]
|
17
|
+
def is_not_empty(&block)
|
18
|
+
if @ratifiable_object.empty?
|
19
|
+
validation_error("Array is empty")
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
instance_eval(&block) if block_given?
|
24
|
+
end#is_not_empty
|
25
|
+
|
26
|
+
|
27
|
+
# Define Minimum Length of Array
|
28
|
+
#
|
29
|
+
# @param [Integer] min_size
|
30
|
+
# @return [void]
|
31
|
+
def min_length(min_size=0, &block)
|
32
|
+
return unless valid_min_length?(min_size)
|
33
|
+
|
34
|
+
instance_eval(&block) if block_given?
|
35
|
+
rescue Exception => e
|
36
|
+
validation_error("#{e.message}")
|
37
|
+
end#min_length
|
38
|
+
|
39
|
+
|
40
|
+
# Define Maximum Length of Array
|
41
|
+
#
|
42
|
+
# @param [Integer] min_size
|
43
|
+
# @return [void]
|
44
|
+
def max_length(max_size=0, &block)
|
45
|
+
return unless valid_max_length?(max_size)
|
46
|
+
|
47
|
+
instance_eval(&block) if block_given?
|
48
|
+
rescue Exception => e
|
49
|
+
validation_error("#{e.message}")
|
50
|
+
end#max_length
|
51
|
+
|
52
|
+
|
53
|
+
# Define length range of Array (inclusive)
|
54
|
+
#
|
55
|
+
# @param [Integer] min_size
|
56
|
+
# @param [Integer] max_size
|
57
|
+
# @return [void]
|
58
|
+
def length_between(min_size=0, max_size=nil, &block)
|
59
|
+
return unless valid_min_length?(min_size)
|
60
|
+
|
61
|
+
if max_size.nil?
|
62
|
+
if min_size == 1
|
63
|
+
validation_error("Consider using is_not_empty")
|
64
|
+
return
|
65
|
+
end
|
66
|
+
else
|
67
|
+
return unless valid_max_length?(max_size)
|
68
|
+
|
69
|
+
if max_size == 0 && min_size == 0
|
70
|
+
validation_error("Consider using is_empty")
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
unless max_size > min_size
|
75
|
+
validation_error("max_size must be greater than min_size")
|
76
|
+
return
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
instance_eval(&block) if block_given?
|
81
|
+
end#length_between
|
82
|
+
private
|
83
|
+
|
84
|
+
# @return [Boolean]
|
85
|
+
def valid_min_length?(min_size)
|
86
|
+
unless min_size.to_i >= 0
|
87
|
+
validation_error("min_length must be a number greater than or equal to 0")
|
88
|
+
return false
|
89
|
+
end
|
90
|
+
|
91
|
+
unless @ratifiable_object.size >= min_size.to_i
|
92
|
+
validation_error("Array length must be #{min_size} or more")
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
return true
|
96
|
+
rescue Exception => e
|
97
|
+
validation_error("#{e.message}")
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Boolean]
|
102
|
+
def valid_max_length?(max_size)
|
103
|
+
unless max_size.to_i >= 0
|
104
|
+
validation_error("max_size must be a number greater than or equal to 0")
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
|
108
|
+
if @ratifiable_object.size > max_size.to_i
|
109
|
+
validation_error("Array length must be less than #{max_size.to_i}")
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
return true
|
114
|
+
rescue Exception => e
|
115
|
+
validation_error("#{e.message}")
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
end#ArrayMethods
|
119
|
+
|
120
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Ratatouille
|
2
|
+
|
3
|
+
# Module used to provide Hash-specific validation methods
|
4
|
+
module HashMethods
|
5
|
+
# Runs validation in block against object for the given key.
|
6
|
+
#
|
7
|
+
# @param [String, Symbol] key
|
8
|
+
def given_key(key, &block)
|
9
|
+
if @ratifiable_object.has_key?(key) && block_given?
|
10
|
+
child_object = Ratatouille::Ratifier.new(@ratifiable_object[key], &block)
|
11
|
+
@errors[key] = child_object.errors unless child_object.valid?
|
12
|
+
end
|
13
|
+
end#given_key
|
14
|
+
|
15
|
+
|
16
|
+
# @return [void]
|
17
|
+
def is_empty(&block)
|
18
|
+
unless @ratifiable_object.empty?
|
19
|
+
validation_error("Hash is not empty")
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
instance_eval(&block) if block_given?
|
24
|
+
end#is_empty
|
25
|
+
|
26
|
+
|
27
|
+
# @return [void]
|
28
|
+
def is_not_empty(&block)
|
29
|
+
if @ratifiable_object.empty?
|
30
|
+
validation_error("Hash is empty")
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
instance_eval(&block) if block_given?
|
35
|
+
end#is_not_empty
|
36
|
+
|
37
|
+
|
38
|
+
# Provide a list of keys that must be present in the Hash to validate. Otherwise,
|
39
|
+
# an error will be added.
|
40
|
+
#
|
41
|
+
# @param [Array] args Array required keys
|
42
|
+
# @return [void]
|
43
|
+
def required_keys(*req_keys, &block)
|
44
|
+
common_keys = (@ratifiable_object.keys & req_keys)
|
45
|
+
|
46
|
+
if @ratifiable_object.empty?
|
47
|
+
validation_error("Cannot find required keys in empty hash.")
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
if req_keys.nil? || req_keys.empty?
|
52
|
+
validation_error("No required keys given to compare Hash against.")
|
53
|
+
return
|
54
|
+
end
|
55
|
+
|
56
|
+
unless common_keys.size == req_keys.size
|
57
|
+
(req_keys - common_keys).each do |missed|
|
58
|
+
case missed
|
59
|
+
when Symbol then validation_error("Missing :#{missed}")
|
60
|
+
when String then validation_error("Missing #{missed}")
|
61
|
+
when respond_to?(:to_s)
|
62
|
+
validation_error("Missing #{missed.to_s}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
instance_eval(&block) if block_given?
|
69
|
+
end#required_keys
|
70
|
+
|
71
|
+
|
72
|
+
# Provide a list of keys to choose from and a choice size (default 1).
|
73
|
+
# When the Hash does not contain at least 'choice_size' keys of the key
|
74
|
+
# list provided, an error will be added.
|
75
|
+
#
|
76
|
+
# @param [Integer] choice_size
|
77
|
+
# @param [Array] args
|
78
|
+
# Array of symbols and/or strings to denote the choices of keys.
|
79
|
+
# All other values are ignored.
|
80
|
+
# @return [void]
|
81
|
+
def choice_of(choice_size=1, *key_list, &block)
|
82
|
+
unless key_list.nil? || key_list.empty?
|
83
|
+
validation_error("choice_of requires a key list to choose from")
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
87
|
+
unless choice_size =~ /\d+/ && choice_size > 0
|
88
|
+
validation_error("choice_of requires a positive integer for choice size")
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
unless key_list.size > choice_size
|
93
|
+
validation_error("Key list size for 'choice_of' should be larger than choice size. Consider using required_keys instead.")
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
97
|
+
common_keys = (@ratifiable_object.keys & key_list)
|
98
|
+
unless common_keys.size == choice_size
|
99
|
+
choices = key_list.collect{|a| String === a || Symbol === a}
|
100
|
+
validation_error("Require #{choice_size} of the following: #{choices.join(', ')}")
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
instance_eval(&block) if block_given?
|
105
|
+
end#choice_of
|
106
|
+
end#HashMethods
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Ratatouille
|
2
|
+
|
3
|
+
# Ratifier acts as a clean room in which to perform validations.
|
4
|
+
class Ratifier
|
5
|
+
attr_reader :errors
|
6
|
+
attr_reader :ratifiable_object
|
7
|
+
|
8
|
+
# A new instance of Ratifier
|
9
|
+
# @param [Hash, Array] obj Object to validate
|
10
|
+
def initialize(obj, options={}, &block)
|
11
|
+
@errors = { "/" => [] }
|
12
|
+
@ratifiable_object = obj
|
13
|
+
|
14
|
+
case obj
|
15
|
+
when Hash then extend Ratatouille::HashMethods
|
16
|
+
when Array then extend Ratatouille::ArrayMethods
|
17
|
+
end
|
18
|
+
|
19
|
+
instance_eval( &block ) if block_given?
|
20
|
+
|
21
|
+
cleanup_errors
|
22
|
+
|
23
|
+
@errors.freeze
|
24
|
+
end#initialize
|
25
|
+
|
26
|
+
|
27
|
+
# Add validation error. Useful for custom validations.
|
28
|
+
# @param [String] str
|
29
|
+
# @param [String] context
|
30
|
+
# @return [void]
|
31
|
+
def validation_error(err_in, context="/")
|
32
|
+
case err_in
|
33
|
+
when String
|
34
|
+
return if err_in.blank?
|
35
|
+
@errors[context] = [] unless @errors[context]
|
36
|
+
@errors[context] << err_in
|
37
|
+
end
|
38
|
+
rescue Exception => e
|
39
|
+
@errors["/"] << e.message
|
40
|
+
end#validation_error
|
41
|
+
|
42
|
+
|
43
|
+
# Does the object pass all validation logic?
|
44
|
+
#
|
45
|
+
# @return [Boolean]
|
46
|
+
def valid?
|
47
|
+
@errors.empty?
|
48
|
+
end#valid?
|
49
|
+
|
50
|
+
|
51
|
+
# If there are no errors in the errors hash, empty it out.
|
52
|
+
#
|
53
|
+
# @return [void]
|
54
|
+
def cleanup_errors
|
55
|
+
@errors = {} if errors_array.empty?
|
56
|
+
rescue Exception => e
|
57
|
+
@errors["/"] << e.message
|
58
|
+
end#cleanup_errors
|
59
|
+
|
60
|
+
|
61
|
+
# @param [Hash] hsh Hash to act upon.
|
62
|
+
# @return [Array]
|
63
|
+
def errors_array(hsh = @errors)
|
64
|
+
return [] unless Hash === hsh
|
65
|
+
all_errs = []
|
66
|
+
|
67
|
+
hsh.each_pair do |k,v|
|
68
|
+
pair_errs = case v
|
69
|
+
when Hash then errors_array(v)
|
70
|
+
when Array then v
|
71
|
+
else []
|
72
|
+
end
|
73
|
+
|
74
|
+
nsed_errs = pair_errs.collect do |e|
|
75
|
+
split_err = e.split("|")
|
76
|
+
|
77
|
+
ctxt, err = "", e
|
78
|
+
ctxt, err = split_err if split_err.size == 2
|
79
|
+
|
80
|
+
case k
|
81
|
+
when String
|
82
|
+
ctxt = "#{k}#{ctxt}" unless k == '/'
|
83
|
+
when Symbol
|
84
|
+
ctxt = ":#{k}#{ctxt}"
|
85
|
+
end
|
86
|
+
|
87
|
+
"/#{ctxt}|#{err}"
|
88
|
+
end
|
89
|
+
|
90
|
+
all_errs << nsed_errs
|
91
|
+
all_errs.flatten!
|
92
|
+
end
|
93
|
+
|
94
|
+
return all_errs
|
95
|
+
end#errors_array
|
96
|
+
end#Ratifier
|
97
|
+
|
98
|
+
end
|
data/lib/ratatouille.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "ratatouille/version"
|
2
|
+
|
3
|
+
require "ratatouille/ratifier"
|
4
|
+
require "ratatouille/nilclass"
|
5
|
+
require "ratatouille/hash"
|
6
|
+
require "ratatouille/array"
|
7
|
+
require "ratatouille/string"
|
8
|
+
|
9
|
+
# Module to provide DSL for validation of complex Hashes
|
10
|
+
module Ratatouille
|
11
|
+
|
12
|
+
# @param [Hash, Array] obj Object to validate
|
13
|
+
# @return [Validatable::Ratifier]
|
14
|
+
def ratify(obj, &block)
|
15
|
+
Ratatouille::Ratifier.new(obj, &block)
|
16
|
+
end#ratify
|
17
|
+
|
18
|
+
end
|
data/ratatouille.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/ratatouille/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "ratatouille"
|
6
|
+
gem.version = Ratatouille::VERSION
|
7
|
+
gem.authors = ["Ryan Johnson"]
|
8
|
+
gem.email = ["rhino.citguy@gmail.com"]
|
9
|
+
gem.homepage = "http://github.com/CITguy/#{gem.name}"
|
10
|
+
gem.summary = %q{DSL for validating complex hashes}
|
11
|
+
gem.description = %q{DSL for validating complex hashes}
|
12
|
+
|
13
|
+
gem.rubyforge_project = 'ratatouille'
|
14
|
+
|
15
|
+
gem.add_development_dependency "rspec", ">= 2.4.0"
|
16
|
+
gem.add_development_dependency "rake"
|
17
|
+
|
18
|
+
gem.files = `git ls-files`.split($\)
|
19
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
20
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
21
|
+
gem.require_paths = ["lib"]
|
22
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Ratatouille::ArrayMethods" do
|
4
|
+
[ :is_empty,
|
5
|
+
:is_not_empty,
|
6
|
+
:min_length,
|
7
|
+
:max_length,
|
8
|
+
:length_between
|
9
|
+
].each do |m|
|
10
|
+
it "block context should respond to #{m}" do
|
11
|
+
r = nil
|
12
|
+
RatifierTest.new([]) { r = self }
|
13
|
+
r.should respond_to m
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "is_empty" do
|
18
|
+
it "should not be valid for non-empty array" do
|
19
|
+
RatifierTest.new(['bar']){ is_empty }.should_not be_valid
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be valid for empty array" do
|
23
|
+
RatifierTest.new([]){ is_empty }.should be_valid
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "is_not_empty" do
|
28
|
+
it "should be valid for non-empty array" do
|
29
|
+
RatifierTest.new(['bar']){ is_not_empty }.should be_valid
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not be valid for empty array" do
|
33
|
+
RatifierTest.new([]){ is_not_empty }.should_not be_valid
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "length_between" do
|
38
|
+
describe "for empty array" do
|
39
|
+
before(:each) do
|
40
|
+
@arr = []
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be valid with 0 min length and any positive, non-zero max_length" do
|
44
|
+
RatifierTest.new(@arr) { length_between(0) }.should be_valid
|
45
|
+
RatifierTest.new(@arr) { length_between(0, 0) }.should_not be_valid
|
46
|
+
RatifierTest.new(@arr) { length_between(0, 1) }.should be_valid
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be invalid with 1 min_length and any max_length" do
|
50
|
+
RatifierTest.new(@arr) { length_between(1) }.should_not be_valid
|
51
|
+
RatifierTest.new(@arr) { length_between(1,1) }.should_not be_valid
|
52
|
+
RatifierTest.new(@arr) { length_between(1,2) }.should_not be_valid
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "for Array with 2 elements" do
|
57
|
+
before(:each) do
|
58
|
+
@arr = ['foo', 'bar']
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should be valid with 1 min_length and any max_length above 1" do
|
62
|
+
RatifierTest.new(@arr) { length_between(1,1) }.should_not be_valid
|
63
|
+
RatifierTest.new(@arr) { length_between(1,2) }.should be_valid
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should be invalid with 0 min_length and any max_length less than 2" do
|
67
|
+
RatifierTest.new(@arr) { length_between(0,0) }.should_not be_valid
|
68
|
+
RatifierTest.new(@arr) { length_between(0,1) }.should_not be_valid
|
69
|
+
RatifierTest.new(@arr) { length_between(0,2) }.should be_valid
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "max_length" do
|
75
|
+
it "should be valid for proper length array with integer argument" do
|
76
|
+
RatifierTest.new([]) { max_length(1) }.should be_valid
|
77
|
+
RatifierTest.new(['foo']) { max_length(1) }.should be_valid
|
78
|
+
|
79
|
+
RatifierTest.new(['foo']) { max_length(0) }.should_not be_valid
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should be invalid for non-numeric length" do
|
83
|
+
RatifierTest.new([]) { max_length({}) }.should_not be_valid
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should be valid for properly transformed float values" do
|
87
|
+
RatifierTest.new(['foo']) { max_length(0.3) }.should_not be_valid
|
88
|
+
RatifierTest.new(['foo']) { max_length(1.3) }.should be_valid
|
89
|
+
RatifierTest.new(['foo', 'bar']) { max_length(1.3) }.should_not be_valid
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "min_length" do
|
94
|
+
it "should be valid for proper length array with integer argument" do
|
95
|
+
RatifierTest.new([]) { min_length(0) }.should be_valid
|
96
|
+
RatifierTest.new([]) { min_length(1) }.should_not be_valid
|
97
|
+
|
98
|
+
RatifierTest.new(['foo']) { min_length(0) }.should be_valid
|
99
|
+
RatifierTest.new(['foo']) { min_length(1) }.should be_valid
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should be invalid for non-numeric length" do
|
103
|
+
RatifierTest.new([]) { min_length({}) }.should_not be_valid
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should be valid for properly transformed float values" do
|
107
|
+
RatifierTest.new([]) { min_length(0.3) }.should be_valid
|
108
|
+
RatifierTest.new([]) { min_length(1.3) }.should_not be_valid
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Ratatouille::HashMethods" do
|
4
|
+
describe "is_empty" do
|
5
|
+
it "should be invalid for non-empty Hash" do
|
6
|
+
RatifierTest.new({:bar => 'biz'}){ is_empty }.should_not be_valid
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should be valid for empty Hash" do
|
10
|
+
RatifierTest.new({}){ is_empty }.should be_valid
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "is_not_empty" do
|
15
|
+
it "should be valid for non-empty hash" do
|
16
|
+
RatifierTest.new({:bar => "biz"}){ is_not_empty }.should be_valid
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not be valid for empty hash" do
|
20
|
+
RatifierTest.new({}){ is_not_empty }.should_not be_valid
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "choice_of" do
|
25
|
+
it "should be invalid if key list is empty" do
|
26
|
+
RatifierTest.new({}) { choice_of(1, []) }.should_not be_valid
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be invalid if choice size less than 1" do
|
30
|
+
RatifierTest.new({}) { choice_of(0, [:foo]) }.should_not be_valid
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be invalid if choice list is not 1 more than choice size" do
|
34
|
+
RatifierTest.new({}) { choice_of(1, [:foo]) }.should_not be_valid
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "required_keys" do
|
39
|
+
it "should be valid if Has contains all required keys" do
|
40
|
+
RatifierTest.new({:foo => "foo"}) { required_keys(:foo, :bar) }.should_not be_valid
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should be invalid if hash is empty and key list is not" do
|
44
|
+
RatifierTest.new({}) { required_keys(:foo) }.should_not be_valid
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should be invalid if Hash does not contain ALL keys in key list" do
|
48
|
+
RatifierTest.new({:foo => "foo"}) { required_keys(:foo, :bar) }.should_not be_valid
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ratatouille::Ratifier do
|
4
|
+
|
5
|
+
it "should be valid on instantiation of new object" do
|
6
|
+
e = RatifierTest.new({})
|
7
|
+
e.should be_valid
|
8
|
+
end
|
9
|
+
|
10
|
+
it "errors should contain one key within block of new instance" do
|
11
|
+
x = {}
|
12
|
+
e = RatifierTest.new({}){ x = @errors }
|
13
|
+
x.keys.size.should == 1
|
14
|
+
x.keys.should == ['/']
|
15
|
+
x['/'].should be_empty
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "validation_error" do
|
19
|
+
it "should create an error when called within a Ratifier block" do
|
20
|
+
test = RatifierTest.new(Object.new) do
|
21
|
+
validation_error("some error")
|
22
|
+
end
|
23
|
+
test.errors_array.should have(1).String
|
24
|
+
|
25
|
+
test = RatifierTest.new({}) do
|
26
|
+
validation_error("some error")
|
27
|
+
validation_error("another error")
|
28
|
+
end
|
29
|
+
test.errors_array.should have(2).String
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not create an error when called with a non-string argument" do
|
33
|
+
test = RatifierTest.new({}) do
|
34
|
+
validation_error({})
|
35
|
+
end
|
36
|
+
test.errors_array.should be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should add the error to the '/' context by default" do
|
40
|
+
test = RatifierTest.new({}) do
|
41
|
+
# NO ERRORS
|
42
|
+
end
|
43
|
+
test.errors['/'].should be_empty
|
44
|
+
|
45
|
+
test = RatifierTest.new({}) do
|
46
|
+
validation_error("foo")
|
47
|
+
end
|
48
|
+
test.errors['/'].should have(1).String
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should add an error to an explicit context (even if it doesn't exist)" do
|
52
|
+
ctxt = "foo"
|
53
|
+
test = RatifierTest.new({}) do
|
54
|
+
# NO ERRORS
|
55
|
+
end
|
56
|
+
test.errors[ctxt].should be_nil
|
57
|
+
|
58
|
+
test = RatifierTest.new({}) do
|
59
|
+
validation_error("broken", ctxt)
|
60
|
+
end
|
61
|
+
test.errors[ctxt].should have(1).String
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "valid?" do
|
66
|
+
it "should be true if errors is empty?" do
|
67
|
+
test = RatifierTest.new({}) do
|
68
|
+
# No Validation = Valid Object
|
69
|
+
end
|
70
|
+
test.valid?.should be_true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "instance variables" do
|
75
|
+
before(:each) do
|
76
|
+
@test = RatifierTest.new({})
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "ratifiable_object" do
|
80
|
+
it "should raise error if modification is attempted" do
|
81
|
+
Proc.new { @test.ratifiable_object = {} }.should raise_error NoMethodError
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "errors" do
|
86
|
+
it "should raise error if modification is attempted" do
|
87
|
+
Proc.new { @test.errors = {} }.should raise_error NoMethodError
|
88
|
+
Proc.new { @test.errors.delete("/") }.should raise_error TypeError
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should be empty on valid object" do
|
92
|
+
ratifier = RatifierTest.new({:foo => "bar"}) do
|
93
|
+
# No Validations = Valid Object
|
94
|
+
end
|
95
|
+
ratifier.errors.should be_empty
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should not be empty on invalid object" do
|
99
|
+
ratifier = RatifierTest.new({:foo => "bar"}) { is_empty }
|
100
|
+
ratifier.errors.should_not be_empty
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "errors_array" do
|
105
|
+
it "should be empty on new Ratifier" do
|
106
|
+
@test.errors_array.should be_empty
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should be empty on valid object" do
|
110
|
+
ratifier = RatifierTest.new({}) do
|
111
|
+
# No Validations = Valid Object
|
112
|
+
end
|
113
|
+
ratifier.errors_array.should be_empty
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should have at least one String item for an invalid object" do
|
117
|
+
test = RatifierTest.new({:foo => "bar"}){ is_empty }
|
118
|
+
test.errors_array.should_not be_empty
|
119
|
+
test.errors_array.should have_at_least(1).String
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Ratatouille
|
4
|
+
|
5
|
+
describe Ratatouille do
|
6
|
+
|
7
|
+
describe "ratify" do
|
8
|
+
it "should return a Ratatouille::Ratifier object" do
|
9
|
+
ratify({}).should be_a Ratatouille::Ratifier
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should evaluate block in context of a Ratatouille::Ratifier object" do
|
13
|
+
o = nil
|
14
|
+
ratify({}) { o = self }
|
15
|
+
o.should be_a Ratatouille::Ratifier
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ratatouille
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Johnson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-04-14 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 31
|
29
|
+
segments:
|
30
|
+
- 2
|
31
|
+
- 4
|
32
|
+
- 0
|
33
|
+
version: 2.4.0
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description: DSL for validating complex hashes
|
51
|
+
email:
|
52
|
+
- rhino.citguy@gmail.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- .gitignore
|
61
|
+
- .rvmrc
|
62
|
+
- .travis-yml
|
63
|
+
- .yardopts
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/ratatouille.rb
|
69
|
+
- lib/ratatouille/array.rb
|
70
|
+
- lib/ratatouille/hash.rb
|
71
|
+
- lib/ratatouille/nilclass.rb
|
72
|
+
- lib/ratatouille/ratifier.rb
|
73
|
+
- lib/ratatouille/string.rb
|
74
|
+
- lib/ratatouille/version.rb
|
75
|
+
- ratatouille.gemspec
|
76
|
+
- spec/lib/ratatouille/array_spec.rb
|
77
|
+
- spec/lib/ratatouille/hash_spec.rb
|
78
|
+
- spec/lib/ratatouille/nilclass_spec.rb
|
79
|
+
- spec/lib/ratatouille/ratifier_spec.rb
|
80
|
+
- spec/lib/ratatouille_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
82
|
+
homepage: http://github.com/CITguy/ratatouille
|
83
|
+
licenses: []
|
84
|
+
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
hash: 3
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
requirements: []
|
109
|
+
|
110
|
+
rubyforge_project: ratatouille
|
111
|
+
rubygems_version: 1.8.10
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: DSL for validating complex hashes
|
115
|
+
test_files:
|
116
|
+
- spec/lib/ratatouille/array_spec.rb
|
117
|
+
- spec/lib/ratatouille/hash_spec.rb
|
118
|
+
- spec/lib/ratatouille/nilclass_spec.rb
|
119
|
+
- spec/lib/ratatouille/ratifier_spec.rb
|
120
|
+
- spec/lib/ratatouille_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
has_rdoc:
|