deeply_valid 0.1.5 → 0.1.7

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.5
1
+ 0.1.7
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{deeply_valid}
8
- s.version = "0.1.5"
8
+ s.version = "0.1.7"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Starr Horne"]
12
- s.date = %q{2010-06-11}
12
+ s.date = %q{2010-06-15}
13
13
  s.description = %q{This gem lets you declaratively specify validations for complex data structures, like those returned from an api.}
14
14
  s.email = %q{starr@chromahq.com}
15
15
  s.extra_rdoc_files = [
@@ -51,8 +51,18 @@ module DeeplyValid
51
51
  (@definitions ||= {})[name.to_sym]
52
52
  end
53
53
 
54
- def structure(name)
55
- Validation.new { |d| self[name.to_sym].valid?(d) }
54
+ def structure(name = nil, &block)
55
+ if name
56
+ Validation.new { |d| self[name.to_sym].valid?(d) }
57
+ elsif block_given?
58
+ block
59
+ else
60
+ raise "structure requires name or block"
61
+ end
62
+ end
63
+
64
+ def value(&block)
65
+ block
56
66
  end
57
67
 
58
68
  end
@@ -6,6 +6,8 @@ module DeeplyValid
6
6
  #
7
7
  class Validation
8
8
 
9
+ attr_accessor :options
10
+
9
11
  #
10
12
  # The initializer defines the conditions that will
11
13
  # satasfy this validation.
@@ -15,12 +17,13 @@ module DeeplyValid
15
17
  # @param [Object] rule When `rule` is any other non-nil object, use `==` to validate
16
18
  # @param [Proc] &block An optional block, which will take one param and return true or false
17
19
  #
18
- def initialize(rule = nil, &block)
20
+ def initialize(rule = nil, options = {}, &block)
19
21
 
20
22
  if rule.nil? && !block_given?
21
23
  raise "No validation rule specified"
22
24
  end
23
25
 
26
+ @options = options
24
27
  @rule = rule
25
28
  @block = block
26
29
  end
@@ -58,8 +61,10 @@ module DeeplyValid
58
61
  def valid_structure?(data, fragment_rule = nil)
59
62
  (fragment_rule || @rule).all? do |k, v|
60
63
 
64
+ v = v.call(@data) if v.is_a?(Proc)
65
+
61
66
  if v.is_a?(Validation)
62
- v.valid?(data[k])
67
+ (v.options[:optional] && !data.has_key?(k)) || v.valid?(data[k])
63
68
 
64
69
  elsif v.is_a?(Regexp)
65
70
  !!(data[k] =~ v)
@@ -67,6 +72,9 @@ module DeeplyValid
67
72
  elsif v.is_a?(Hash)
68
73
  valid_structure?(data[k], v)
69
74
 
75
+ elsif v.nil?
76
+ !data.has_key?(k)
77
+
70
78
  else
71
79
  data[k] == v
72
80
  end
@@ -81,10 +89,17 @@ module DeeplyValid
81
89
  #
82
90
  def valid?(data)
83
91
 
84
- if @rule.kind_of?(Regexp)
92
+ @data = data
93
+
94
+ rule = @rule.is_a?(Proc) ? @rule.call(@data) : @rule
95
+
96
+ result = if rule.kind_of?(Regexp)
85
97
  valid_pattern?(data)
86
98
 
87
- elsif @rule.kind_of?(Hash)
99
+ elsif rule.kind_of?(Validation)
100
+ rule.valid?(data)
101
+
102
+ elsif rule.kind_of?(Hash)
88
103
  valid_structure?(data)
89
104
 
90
105
  elsif @block
@@ -94,6 +109,10 @@ module DeeplyValid
94
109
  data == @rule
95
110
  end
96
111
 
112
+ @data = nil
113
+
114
+ result
115
+
97
116
  end
98
117
 
99
118
  end
@@ -1,4 +1,5 @@
1
1
  require 'json'
2
+ require 'date'
2
3
 
3
4
  module DeeplyValid
4
5
 
@@ -66,23 +67,23 @@ module DeeplyValid
66
67
  end
67
68
 
68
69
  #
69
- # Validates datetime with optional limit
70
+ # Validates time with optional limit
70
71
  #
71
72
  # @param [Integer, Range] limit
72
73
  # @return [Validation] The validation
73
74
  #
74
- def datetime(limit = nil)
75
- Validation.new { |d| d.is_a?(DateTime) && in_range?(d, limit) }
75
+ def time(limit = nil)
76
+ Validation.new { |d| d.is_a?(Time) && in_range?(d, limit) }
76
77
  end
77
78
 
78
79
  #
79
- # Validates time with optional limit
80
+ # Validates nil. Normally a nil means "don't validate". Use this
81
+ # if you're expecting an actual nil
80
82
  #
81
- # @param [Integer, Range] limit
82
83
  # @return [Validation] The validation
83
84
  #
84
- def time(limit = nil)
85
- Validation.new { |d| d.is_a?(Time) && in_range?(d, limit) }
85
+ def nil_value
86
+ Validation.new { |d| d.nil? }
86
87
  end
87
88
 
88
89
  #
@@ -228,8 +229,29 @@ module DeeplyValid
228
229
  range = [range] unless range.respond_to?(:include?)
229
230
  range.include?(val)
230
231
  end
232
+ end
233
+
234
+ #
235
+ # Set a validation as optional. This only has any effect
236
+ # if the validation is part of a structure.
237
+ #
238
+ # The following validation:
239
+ #
240
+ # {:key => optional(integer)}
241
+ #
242
+ # Will validate both:
243
+ #
244
+ # {:key => 1}
245
+ # {}
246
+ #
247
+ # @param [Validation] validation
248
+ # @return [Validation]
249
+ #
250
+ def optional(validation)
251
+ validation.options[:optional] = true
252
+ validation
253
+ end
231
254
 
232
- end
233
255
  end
234
256
  end
235
257
  end
@@ -12,6 +12,7 @@ class BaseTest < Test::Unit::TestCase
12
12
  define :manual, DeeplyValid::Validation.new { |d| d > 10 }
13
13
  define :literal, "x"
14
14
  define :hash, { :key => "val" }
15
+ define :nested_self_reference, structure { |data| structure(:regexp) }
15
16
  end
16
17
  end
17
18
 
@@ -35,6 +36,12 @@ class BaseTest < Test::Unit::TestCase
35
36
  assert Sample[:self_reference].valid?("abc")
36
37
  assert !Sample[:self_reference].valid?("123")
37
38
  end
39
+
40
+ should "Handle nested self-reference" do
41
+ assert Sample[:nested_self_reference]
42
+ assert Sample[:nested_self_reference].valid?("abc")
43
+ assert !Sample[:nested_self_reference].valid?("123")
44
+ end
38
45
 
39
46
  end
40
47
 
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
+ begin; require 'turn'; rescue LoadError; end
4
5
 
5
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -16,7 +16,10 @@ class SystemTest < Test::Unit::TestCase
16
16
  :name => any('sales', 'accounting', 'engineering'),
17
17
  :building => integer
18
18
  },
19
- :performace_reviews => array( structure(:review) )
19
+ :performace_reviews => array( structure(:review) ),
20
+ :pension => value { |data| integer if data[:age] > 50 },
21
+ :hobby => optional(string)
22
+
20
23
  }
21
24
 
22
25
  # This structure is referenced in the "person" structure
@@ -26,7 +29,7 @@ class SystemTest < Test::Unit::TestCase
26
29
  }
27
30
  end
28
31
 
29
- @valid_person = {
32
+ @valid_person1 = {
30
33
  :id => "x"*32,
31
34
  :age => 22,
32
35
  :name => "Bob Jones",
@@ -40,10 +43,44 @@ class SystemTest < Test::Unit::TestCase
40
43
  ]
41
44
  }
42
45
 
43
- @invalid_person = {
46
+ # this person is over 50, so there's a value for pension
47
+ @valid_person2 = {
48
+ :id => "x"*32,
49
+ :age => 61,
50
+ :name => "Bob Jones",
51
+ :pension => 666,
52
+ :hobby => "boating",
53
+ :department => {
54
+ :name => "sales",
55
+ :building => 33
56
+ },
57
+ :performace_reviews => [
58
+ { :author => "joe", :body => "a review" },
59
+ { :author => "bill", :body => "another review" }
60
+ ]
61
+ }
62
+
63
+ # this person is under 50, and has a value for pension,
64
+ # so the record is invalid.
65
+ @invalid_person1 = {
44
66
  :id => "x"*32,
45
67
  :age => 22,
46
68
  :name => "Bob Jones",
69
+ :pension => 666,
70
+ :department => {
71
+ :name => "sales",
72
+ :building => 33
73
+ },
74
+ :performace_reviews => [
75
+ { :author => "joe", :body => "a review" },
76
+ { :author => "bill", :body => "another review" }
77
+ ]
78
+ }
79
+
80
+ @invalid_person2 = {
81
+ :id => "x"*32,
82
+ :age => 32,
83
+ :name => "Bob Jones",
47
84
  :department => {
48
85
  :name => "sales",
49
86
  :building => 33
@@ -57,8 +94,10 @@ class SystemTest < Test::Unit::TestCase
57
94
  end
58
95
 
59
96
  should "validate correctly" do
60
- assert MyValidations[:person].valid?(@valid_person)
61
- assert !MyValidations[:person].valid?(@invalid_person)
97
+ assert MyValidations[:person].valid?(@valid_person1)
98
+ assert MyValidations[:person].valid?(@valid_person2)
99
+ assert !MyValidations[:person].valid?(@invalid_person1)
100
+ assert !MyValidations[:person].valid?(@invalid_person2)
62
101
  end
63
102
 
64
103
  end
@@ -92,17 +92,8 @@ class ValidationHelpersTest < Test::Unit::TestCase
92
92
  context "date helper" do
93
93
 
94
94
  should "validate correctly" do
95
- assert Sample.date.valid?(Date.today)
96
- assert !Sample.date(:before => Date.today).valid?(Date.today)
97
- end
98
-
99
- end
100
-
101
- context "datetime helper" do
102
-
103
- should "validate correctly" do
104
- assert Sample.datetime.valid?(DateTime.now)
105
- assert !Sample.datetime(:before => DateTime.now).valid?(DateTime.now)
95
+ assert Sample.date.valid?(Date.civil(2010, 3, 10))
96
+ assert !Sample.date(:before => Date.civil(2010, 3, 10)).valid?(Date.civil(2010, 4, 10))
106
97
  end
107
98
 
108
99
  end
@@ -101,4 +101,42 @@ class ValidationTest < Test::Unit::TestCase
101
101
 
102
102
  end
103
103
 
104
+ context "A structure validation for {:key => nil}" do
105
+ setup do
106
+ rule = { :key => nil, :bogus => 1 }
107
+ @validation = Validation.new(rule)
108
+ end
109
+
110
+ should_validate_all([
111
+ {:bogus => 1}
112
+ ])
113
+
114
+ should_not_validate_any([
115
+ {:key => false, :bogus => 1},
116
+ {:key => true, :bogus => 1},
117
+ {:key => nil, :bogus => 1},
118
+ {:key => "sesame", :bogus => 1},
119
+ ])
120
+
121
+ end
122
+
123
+ context "A structure with an optional validation" do
124
+ setup do
125
+ rule = { :key => Validation.new(1, :optional => true) }
126
+ @validation = Validation.new(rule)
127
+ end
128
+
129
+ should_validate_all([
130
+ {:key => 1},
131
+ {}
132
+ ])
133
+
134
+ should_not_validate_any([
135
+ {:key => 2},
136
+ {:key => nil},
137
+ {:key => false}
138
+ ])
139
+
140
+ end
141
+
104
142
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 5
9
- version: 0.1.5
8
+ - 7
9
+ version: 0.1.7
10
10
  platform: ruby
11
11
  authors:
12
12
  - Starr Horne
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-06-11 00:00:00 -05:00
17
+ date: 2010-06-15 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency