validate 1.0.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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +1 -0
- data/lib/validate/parser.rb +65 -0
- data/lib/validate/validations.rb +107 -0
- data/lib/validate/validator.rb +36 -0
- data/lib/validate/version.rb +4 -0
- data/lib/validate.rb +30 -0
- data/spec/validate_spec.rb +445 -0
- data/validate.gemspec +23 -0
- metadata +75 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kenneth Ballenegger
|
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,101 @@
|
|
1
|
+
# Validations
|
2
|
+
|
3
|
+
Validations is a validations library that can validate *any* object that can be
|
4
|
+
converted to a hash using `to_hash`.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'validations'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install validations
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
This is what a validation looks like, with this library:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
# Here, we have a custom DSL for expressing validations. We put the
|
26
|
+
# validations for the model in an extension which we require
|
27
|
+
# independently. Doing `include Validations` in the module adds
|
28
|
+
# the required method to make the DSL work, and adds a `validates`
|
29
|
+
# method which will perform the validations are return whether they
|
30
|
+
# pass, or whether there are any errors.
|
31
|
+
|
32
|
+
module Store
|
33
|
+
|
34
|
+
module ItemValidations
|
35
|
+
|
36
|
+
# Order matters, first include library. Also: validations are
|
37
|
+
# executed and checked in the order in which they are defined.
|
38
|
+
include Validations
|
39
|
+
|
40
|
+
# Validations are contained within a block, so as to not pollute
|
41
|
+
# the class namespace with a DSL's implementation.
|
42
|
+
validations do
|
43
|
+
|
44
|
+
# simple ActiveRecord style validation
|
45
|
+
validates_presence_of :name
|
46
|
+
|
47
|
+
# multiple keys can be included in one validation
|
48
|
+
validates_type_of :description, :long_description, is: String
|
49
|
+
|
50
|
+
# or the key could be validated with a block
|
51
|
+
validates :type, with: -> { ['currency', 'iap'].include?(self) }
|
52
|
+
|
53
|
+
# or we could build a validation for enums
|
54
|
+
validates_inclusion_of :reward_type, in: %w(currency consumable permanent)
|
55
|
+
|
56
|
+
# inline `when`, validation only performed when block returns true
|
57
|
+
validates_numericality_of :amount, when: -> { type == :currency }
|
58
|
+
validates_type_of :currency, is: String, when: -> { type == :currency }
|
59
|
+
|
60
|
+
# or alternatively `when` as a block
|
61
|
+
run_when -> { type == :currency } do
|
62
|
+
validates_numericality_of :amount
|
63
|
+
validates_type_of :currency, is: String
|
64
|
+
end
|
65
|
+
|
66
|
+
# also, validates subdocuments
|
67
|
+
validates_child_hash :iap, when: -> { type == :iap } do
|
68
|
+
# the validation is now scoped to the subdocument,
|
69
|
+
# ie. we're validating the sub-doc
|
70
|
+
validates_type_of :id, is: BSON::ObjectId
|
71
|
+
validates_type_of :iap_id, is: String
|
72
|
+
validates_type_of :tier, is: Numeric
|
73
|
+
end
|
74
|
+
|
75
|
+
# arrays can be validated, too:
|
76
|
+
validates_array :tags do
|
77
|
+
# use regular validate function with the key self to
|
78
|
+
# validate array elements
|
79
|
+
validates_type_of :self, is: String
|
80
|
+
end
|
81
|
+
|
82
|
+
# arrays of hashes can be validated by just adding regular
|
83
|
+
# validations inside the block
|
84
|
+
validates_array :logic do
|
85
|
+
validates_inclusion_of :type, in: [:one, :two]
|
86
|
+
validates_type_of :one, is: String, when: -> { type == :one }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
::Kongo::Model.add_extension(:store_items, ItemValidations)
|
91
|
+
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
1. Fork it
|
98
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
99
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
100
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
101
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,65 @@
|
|
1
|
+
|
2
|
+
module Validate
|
3
|
+
|
4
|
+
# The rules for parsing validations are such:
|
5
|
+
#
|
6
|
+
# - Validations are method calls (starting with the string `validates_`)
|
7
|
+
# - followed by field names as regular arguments (as symbols)
|
8
|
+
# - any options are included in an options hash, eg. is: String
|
9
|
+
# - and native blocks are reserved for children-validations
|
10
|
+
#
|
11
|
+
# For example:
|
12
|
+
#
|
13
|
+
# validates_subhash :iap1, :iap2, when: -> { type == :iap } do
|
14
|
+
# validates_type_of :id, is: String
|
15
|
+
# validates_type_of :tier, is: Numeric
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
class BlockParsingContext
|
19
|
+
|
20
|
+
def self.parse(&block)
|
21
|
+
# execute block, return array of validation methods called
|
22
|
+
context = BlockParsingContext.new
|
23
|
+
context.instance_exec(&block)
|
24
|
+
context.validations
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@validations = []
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :validations
|
32
|
+
|
33
|
+
def method_missing(method, *args, &block)
|
34
|
+
raise NoMethodError.new("No method #{method} to call in the context of a validation block.") unless method.to_s =~ /^validates/
|
35
|
+
raise NoMethodError.new("Undefined validation method: #{method}...") unless ValidationMethods.respond_to?(method)
|
36
|
+
opts = args.pop if args.last.is_a?(::Hash)
|
37
|
+
children = if block
|
38
|
+
BlockParsingContext.parse(&block)
|
39
|
+
end
|
40
|
+
@validations << {
|
41
|
+
name: method,
|
42
|
+
fields: args,
|
43
|
+
opts: opts,
|
44
|
+
validations: children
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# `when` is a special case, its syntax is as follows:
|
49
|
+
#
|
50
|
+
# when -> { ... } do
|
51
|
+
# # validations go here
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
def run_when(condition, &block)
|
55
|
+
validations = BlockParsingContext.parse(&block)
|
56
|
+
validations.map do |v|
|
57
|
+
v[:opts] ||= {}
|
58
|
+
v[:opts][:when] = condition
|
59
|
+
v
|
60
|
+
end
|
61
|
+
@validations += validations
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
|
2
|
+
module Validate
|
3
|
+
|
4
|
+
# Every validation method has four arguments:
|
5
|
+
#
|
6
|
+
# obj: the `to_hash` of the object to validate
|
7
|
+
# field: the field to validate
|
8
|
+
# opts: the options for the validation
|
9
|
+
# validator: a Validator object that can be used for children
|
10
|
+
#
|
11
|
+
module ValidationMethods
|
12
|
+
|
13
|
+
# Validate a field by executing a block in the context of the field.
|
14
|
+
#
|
15
|
+
# `self` in the block is bound to the field's value, or `nil`.
|
16
|
+
#
|
17
|
+
# validates :type, with: -> { is_a?(String) && self =~ /^a/ }
|
18
|
+
#
|
19
|
+
def self.validates(obj, field, opts, validator)
|
20
|
+
true == obj[field].instance_exec(&opts[:with])
|
21
|
+
end
|
22
|
+
|
23
|
+
# Validates that a field exists.
|
24
|
+
#
|
25
|
+
# `nil` is acceptable as a value for the field.
|
26
|
+
#
|
27
|
+
# validates_presence_of :field
|
28
|
+
#
|
29
|
+
def self.validates_presence_of(obj, field, opts, validator)
|
30
|
+
obj.include?(field)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Validates that a field exists and is an instance of a class or module.
|
34
|
+
#
|
35
|
+
# validates_type_of :name, is: String
|
36
|
+
#
|
37
|
+
def self.validates_type_of(obj, field, opts, validator)
|
38
|
+
obj.include?(field) && obj[field].is_a?(opts[:is])
|
39
|
+
end
|
40
|
+
|
41
|
+
# Validates that the value of the field appears in an array.
|
42
|
+
#
|
43
|
+
# validates_inclusion_of :type, in: %w(paid free)
|
44
|
+
#
|
45
|
+
def self.validates_inclusion_of(obj, field, opts, validator)
|
46
|
+
opts[:in].include?(obj[field])
|
47
|
+
end
|
48
|
+
|
49
|
+
# Validates that a field's value is numeric.
|
50
|
+
#
|
51
|
+
# validates_numericality_of :amount
|
52
|
+
#
|
53
|
+
def self.validates_numericality_of(obj, field, opts, validator)
|
54
|
+
obj[field].is_a?(Numeric)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Validates the value, ensure equality.
|
58
|
+
#
|
59
|
+
# validates_value_of :field, is: 'something'
|
60
|
+
#
|
61
|
+
def self.validates_value_of(obj, field, opts, validator)
|
62
|
+
obj.include?(field) && obj[field] == opts[:is]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Validates a Hash field with its own set of validations.
|
66
|
+
#
|
67
|
+
# validates_child_hash :hash do
|
68
|
+
# validates_value_of :type, is: 'price'
|
69
|
+
# validates_numericality_of :amount
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
def self.validates_child_hash(obj, field, opts, validator)
|
73
|
+
return false unless obj[field].respond_to?(:to_hash)
|
74
|
+
hash = obj[field].to_hash
|
75
|
+
validator.validates?(hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Validates each element in an Array with a set of validations.
|
79
|
+
#
|
80
|
+
# *Note:* the children validations should look at the field `:self` to
|
81
|
+
# contain the value to be validated. ie. it validates {self: element}
|
82
|
+
#
|
83
|
+
# # ensures :elements is an array of strings
|
84
|
+
# validates_array :elements do
|
85
|
+
# validates_type_of :self, is: String
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
def self.validates_array(obj, field, opts, validator)
|
89
|
+
return false unless obj[field].respond_to?(:to_a)
|
90
|
+
array = obj[field].to_a
|
91
|
+
array.map do |e|
|
92
|
+
validator.validates?({self: e})
|
93
|
+
end.reduce {|a,b| a && b }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Validates a field against a regular expression.
|
97
|
+
#
|
98
|
+
# validates_regex :field, matches: /^hello/
|
99
|
+
#
|
100
|
+
def self.validates_regex(obj, field, opts, validator)
|
101
|
+
return false unless obj[field].respond_to?(:=~)
|
102
|
+
0 == (obj[field] =~ opts[:matches])
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
module Validate
|
3
|
+
|
4
|
+
# Actual implementation
|
5
|
+
#
|
6
|
+
class Validator
|
7
|
+
|
8
|
+
def initialize(validations)
|
9
|
+
@validations = validations
|
10
|
+
end
|
11
|
+
|
12
|
+
def validates?(context)
|
13
|
+
bool = @validations
|
14
|
+
.select do |v|
|
15
|
+
# `:when` is a special case, this gets processed right away and
|
16
|
+
# filtered out...
|
17
|
+
!(v[:opts] || {})[:when].is_a?(Proc) || context.instance_exec(&v[:opts][:when])
|
18
|
+
end
|
19
|
+
.map do |v|
|
20
|
+
# destructure fields
|
21
|
+
v[:fields].map {|f| v.merge(fields: f) }
|
22
|
+
end.flatten(1)
|
23
|
+
.map do |v|
|
24
|
+
# lastly, execute validation
|
25
|
+
validator = if v[:validations]
|
26
|
+
Validator.new(v[:validations])
|
27
|
+
end
|
28
|
+
ValidationMethods.send(v[:name], context.to_hash, v[:fields], v[:opts], validator)
|
29
|
+
end
|
30
|
+
.reduce {|a,b| a && b }
|
31
|
+
# return the result as a boolean
|
32
|
+
bool.nil? ? true : bool
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/validate.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'validate/version'
|
2
|
+
require 'validate/validations'
|
3
|
+
require 'validate/parser'
|
4
|
+
require 'validate/validator'
|
5
|
+
|
6
|
+
|
7
|
+
module Validate
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Doubles as setter & getter for @validations
|
11
|
+
# This method is also the main interface to this lib.
|
12
|
+
#
|
13
|
+
def validations(&block)
|
14
|
+
return @validator unless block
|
15
|
+
validations = BlockParsingContext.parse(&block)
|
16
|
+
@validator = Validator.new(validations)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.included(klass)
|
21
|
+
klass.extend(ClassMethods)
|
22
|
+
end
|
23
|
+
|
24
|
+
# May throw a nil NoMethodError if validations are not defined properly.
|
25
|
+
#
|
26
|
+
def validates?
|
27
|
+
self.class.validations.validates?(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,445 @@
|
|
1
|
+
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
$: << File.dirname(__FILE__) + '/../lib'
|
5
|
+
require 'validate'
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
class BaseTestClass
|
10
|
+
def initialize(hash)
|
11
|
+
@hash = hash
|
12
|
+
end
|
13
|
+
def to_hash
|
14
|
+
@hash
|
15
|
+
end
|
16
|
+
include Validate
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Validate do
|
20
|
+
|
21
|
+
context 'simple test' do
|
22
|
+
|
23
|
+
before do
|
24
|
+
class TestClass < BaseTestClass
|
25
|
+
validations do
|
26
|
+
validates_presence_of 'name', when: -> { true }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should validate a valid object' do
|
32
|
+
test = TestClass.new({"name" => :hello})
|
33
|
+
test.validates?.should == true
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should fail to validate an invalid object' do
|
37
|
+
test = TestClass.new({"not_name" => :hello})
|
38
|
+
test.validates?.should == false
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'multiple fields' do
|
44
|
+
|
45
|
+
before do
|
46
|
+
class TestClass < BaseTestClass
|
47
|
+
validations do
|
48
|
+
validates_presence_of :field1, :field2
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'validate' do
|
54
|
+
test = TestClass.new(field1: true, field2: true)
|
55
|
+
test.validates?.should == true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'fail' do
|
59
|
+
test = TestClass.new(field1: true)
|
60
|
+
test.validates?.should == false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'when option' do
|
65
|
+
before do
|
66
|
+
class TestClass < BaseTestClass
|
67
|
+
validations do
|
68
|
+
validates_presence_of :name, when: -> { @hash[:something] == true }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'validates when when does not match' do
|
74
|
+
test = TestClass.new(something: false)
|
75
|
+
test.validates?.should == true
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'validates when when does match' do
|
79
|
+
test = TestClass.new(something: true, name: 'me')
|
80
|
+
test.validates?.should == true
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'fails' do
|
84
|
+
test = TestClass.new(something: true)
|
85
|
+
test.validates?.should == false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'run_when block' do
|
90
|
+
before do
|
91
|
+
class TestClass < BaseTestClass
|
92
|
+
validations do
|
93
|
+
run_when -> { @hash[:something] == true } do
|
94
|
+
validates_presence_of :name
|
95
|
+
validates_numericality_of :amount
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'validates when when does not match' do
|
102
|
+
test = TestClass.new(something: false)
|
103
|
+
test.validates?.should == true
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'validates when when does match' do
|
107
|
+
test = TestClass.new(something: true, name: 'me', amount: 1.3)
|
108
|
+
test.validates?.should == true
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'fails when one validations fail' do
|
112
|
+
test = TestClass.new(something: true, name: 'me')
|
113
|
+
test.validates?.should == false
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'fails when all validations fail' do
|
117
|
+
test = TestClass.new(something: true)
|
118
|
+
test.validates?.should == false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'validates_presence_of' do
|
123
|
+
|
124
|
+
before do
|
125
|
+
class TestClass < BaseTestClass
|
126
|
+
validations do
|
127
|
+
validates_presence_of :field
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'validates' do
|
133
|
+
test = TestClass.new(field: :something)
|
134
|
+
test.validates?.should == true
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'fails' do
|
138
|
+
test = TestClass.new(random: :field_name)
|
139
|
+
test.validates?.should == false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'validates_type_of' do
|
144
|
+
|
145
|
+
before do
|
146
|
+
class TestClass < BaseTestClass
|
147
|
+
validations do
|
148
|
+
validates_type_of :field, is: Symbol
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'validates' do
|
154
|
+
test = TestClass.new(field: :symbol)
|
155
|
+
test.validates?.should == true
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'fails' do
|
159
|
+
test = TestClass.new(field: 'symbol')
|
160
|
+
test.validates?.should == false
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'validates' do
|
165
|
+
|
166
|
+
before do
|
167
|
+
class TestClass < BaseTestClass
|
168
|
+
validations do
|
169
|
+
validates :field, with: -> { self[:test] == :val }
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'validates' do
|
175
|
+
test = TestClass.new(field: {test: :val})
|
176
|
+
test.validates?.should == true
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'fails' do
|
180
|
+
test = TestClass.new(field: {test: nil})
|
181
|
+
test.validates?.should == false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'validates_inclusion_of' do
|
186
|
+
|
187
|
+
before do
|
188
|
+
class TestClass < BaseTestClass
|
189
|
+
validations do
|
190
|
+
validates_inclusion_of :field, in: %w(one two three)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'validates' do
|
196
|
+
test = TestClass.new(field: 'two')
|
197
|
+
test.validates?.should == true
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'fails' do
|
201
|
+
test = TestClass.new(field: {test: nil})
|
202
|
+
test.validates?.should == false
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'validates_numericality_of' do
|
207
|
+
|
208
|
+
before do
|
209
|
+
class TestClass < BaseTestClass
|
210
|
+
validations do
|
211
|
+
validates_numericality_of :field
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'validates' do
|
217
|
+
test = TestClass.new(field: 1.3)
|
218
|
+
test.validates?.should == true
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'fails' do
|
222
|
+
test = TestClass.new(field: {test: nil})
|
223
|
+
test.validates?.should == false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'validates_value_of' do
|
228
|
+
|
229
|
+
before do
|
230
|
+
class TestClass < BaseTestClass
|
231
|
+
validations do
|
232
|
+
validates_value_of :field, is: :something
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'validates' do
|
238
|
+
test = TestClass.new(field: :something)
|
239
|
+
test.validates?.should == true
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'fails' do
|
243
|
+
test = TestClass.new(field: :something_else)
|
244
|
+
test.validates?.should == false
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'validates_regex' do
|
249
|
+
|
250
|
+
before do
|
251
|
+
class TestClass < BaseTestClass
|
252
|
+
validations do
|
253
|
+
validates_regex :field, matches: /^hello/
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'validates' do
|
259
|
+
test = TestClass.new(field: 'hello world')
|
260
|
+
test.validates?.should == true
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'fails' do
|
264
|
+
test = TestClass.new(field: 'bye world')
|
265
|
+
test.validates?.should == false
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context 'validates_child_hash' do
|
270
|
+
|
271
|
+
before do
|
272
|
+
class TestClass < BaseTestClass
|
273
|
+
validations do
|
274
|
+
validates_child_hash :field do
|
275
|
+
validates_value_of :type, is: 'price'
|
276
|
+
validates_numericality_of :amount
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'validates' do
|
283
|
+
test = TestClass.new(field: {type: 'price', amount: 1.3})
|
284
|
+
test.validates?.should == true
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'fails when not a hash' do
|
288
|
+
test = TestClass.new(field: nil)
|
289
|
+
test.validates?.should == false
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'fails when all validations fail' do
|
293
|
+
test = TestClass.new(field: {random: :stuff})
|
294
|
+
test.validates?.should == false
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'fails when only one validation fails' do
|
298
|
+
test = TestClass.new(field: {type: :random, amount: 20})
|
299
|
+
test.validates?.should == false
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
context 'validates_array' do
|
305
|
+
|
306
|
+
before do
|
307
|
+
class TestClass < BaseTestClass
|
308
|
+
validations do
|
309
|
+
validates_array :field do
|
310
|
+
validates_type_of :self, is: String
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'validates' do
|
317
|
+
test = TestClass.new(field: %w(one two three))
|
318
|
+
test.validates?.should == true
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'fails when not a hash' do
|
322
|
+
test = TestClass.new(field: 'not an array')
|
323
|
+
test.validates?.should == false
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'fails when all validations fail' do
|
327
|
+
test = TestClass.new(field: [1, 2, 3])
|
328
|
+
test.validates?.should == false
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'fails when only one validation fails' do
|
332
|
+
test = TestClass.new(field: ['one', 'two', 3])
|
333
|
+
test.validates?.should == false
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
context 'complex example' do
|
339
|
+
|
340
|
+
before do
|
341
|
+
|
342
|
+
class TestClass < BaseTestClass
|
343
|
+
|
344
|
+
validations do
|
345
|
+
validates_type_of :id, is: Integer
|
346
|
+
validates_type_of :name, :description, is: String
|
347
|
+
|
348
|
+
validates_presence_of :secret
|
349
|
+
|
350
|
+
validates_inclusion_of :type, in: %w(paid free)
|
351
|
+
|
352
|
+
# validate a block in the data structure that must evaluate to true
|
353
|
+
validates :block, with: -> { self.call == true }
|
354
|
+
|
355
|
+
# number_or_symbols is an array that contains either numerics or
|
356
|
+
# symbols... only when it is set
|
357
|
+
validates_array :number_or_symbols, when: -> { @hash.include?(:number_or_symbols) } do
|
358
|
+
|
359
|
+
validates :self, with: -> { self.is_a?(String) || self.is_a?(Numeric) }
|
360
|
+
|
361
|
+
# when it is numeric, it must be greater than 0
|
362
|
+
validates :self,
|
363
|
+
when: -> { self[:self].is_a?(Numeric) },
|
364
|
+
with: -> { self > 0 }
|
365
|
+
|
366
|
+
# when it is a symbol, it start with `a` and is > 2 chars
|
367
|
+
run_when -> { self[:self].is_a?(String) } do
|
368
|
+
validates_regex :self, matches: /^a/
|
369
|
+
validates_regex :self, matches: /..+/
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
validates_child_hash :iap do
|
374
|
+
validates_type_of :id, is: Numeric
|
375
|
+
validates_type_of :bundle, is: String
|
376
|
+
validates_regex :bundle, matches: /(\w+\.){2}\w+/
|
377
|
+
end
|
378
|
+
validates :iap, with: -> { self.keys.count == 2 }
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
@valid_hash = {
|
383
|
+
id: 1,
|
384
|
+
name: 'item',
|
385
|
+
description: 'string',
|
386
|
+
secret: :anything,
|
387
|
+
type: 'paid',
|
388
|
+
block: -> { true },
|
389
|
+
number_or_symbols: [
|
390
|
+
1, 2, 3,
|
391
|
+
'awesome', 'awful'
|
392
|
+
],
|
393
|
+
iap: {
|
394
|
+
id: 1,
|
395
|
+
bundle: 'com.chartboost.awesome'
|
396
|
+
}
|
397
|
+
}
|
398
|
+
end
|
399
|
+
|
400
|
+
it 'validates' do
|
401
|
+
test = TestClass.new(@valid_hash)
|
402
|
+
test.validates?.should == true
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'fails when block is changed' do
|
406
|
+
hash = @valid_hash.dup
|
407
|
+
hash[:block] = -> { false }
|
408
|
+
test = TestClass.new(hash)
|
409
|
+
test.validates?.should == false
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'fails when number_or_symbols contains invalid numbers' do
|
413
|
+
hash = @valid_hash.dup
|
414
|
+
hash[:number_or_symbols] = [0, -1, 'awesome']
|
415
|
+
test = TestClass.new(hash)
|
416
|
+
test.validates?.should == false
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'fails when number_or_symbols contains invalid symbols' do
|
420
|
+
hash = @valid_hash.dup
|
421
|
+
hash[:number_or_symbols] = ['sick', 'awesome', 3, 'awful']
|
422
|
+
test = TestClass.new(hash)
|
423
|
+
test.validates?.should == false
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'fails when secret does not exist' do
|
427
|
+
hash = @valid_hash.dup
|
428
|
+
hash.delete(:secret)
|
429
|
+
test = TestClass.new(hash)
|
430
|
+
test.validates?.should == false
|
431
|
+
end
|
432
|
+
|
433
|
+
it 'fails when number_or_symbols contains invalid numbers' do
|
434
|
+
hash = @valid_hash.dup
|
435
|
+
hash[:iap] = {
|
436
|
+
id: 1,
|
437
|
+
bundle: 'hello'
|
438
|
+
}
|
439
|
+
test = TestClass.new(hash)
|
440
|
+
test.validates?.should == false
|
441
|
+
end
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
|
data/validate.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'validate/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'validate'
|
8
|
+
gem.version = Validate::VERSION
|
9
|
+
gem.authors = ['Kenneth Ballenegger']
|
10
|
+
gem.email = ['kenneth@ballenegger.com']
|
11
|
+
gem.description = %q{Validations is a library for validating data structures.}
|
12
|
+
gem.summary = %q{Validations is a library for validating data structures.}
|
13
|
+
gem.homepage = ''
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
# dependencies
|
21
|
+
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: validate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kenneth Ballenegger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Validations is a library for validating data structures.
|
31
|
+
email:
|
32
|
+
- kenneth@ballenegger.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- lib/validate.rb
|
43
|
+
- lib/validate/parser.rb
|
44
|
+
- lib/validate/validations.rb
|
45
|
+
- lib/validate/validator.rb
|
46
|
+
- lib/validate/version.rb
|
47
|
+
- spec/validate_spec.rb
|
48
|
+
- validate.gemspec
|
49
|
+
homepage: ''
|
50
|
+
licenses: []
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.8.24
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Validations is a library for validating data structures.
|
73
|
+
test_files:
|
74
|
+
- spec/validate_spec.rb
|
75
|
+
has_rdoc:
|