nested_validator 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.
- checksums.yaml +15 -0
- data/.gitignore +0 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +22 -0
- data/README.md +194 -0
- data/Rakefile +6 -0
- data/lib/nested_validator.rb +3 -0
- data/lib/nested_validator/nested_validator.rb +71 -0
- data/lib/nested_validator/validate_nested_matcher.rb +135 -0
- data/lib/nested_validator/version.rb +3 -0
- data/nested_validator.gemspec +32 -0
- data/spec/lib/nested_validator/nested_validator_spec.rb +212 -0
- data/spec/lib/nested_validator/usage_spec.rb +102 -0
- data/spec/lib/nested_validator/validate_nested_matcher_spec.rb +176 -0
- data/spec/spec_helper.rb +16 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MDVlMmQxNWU4MjFkZjYxMmZkY2I2OGVlNzM0YzE4NzcwMGMwMjExYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTk5MmQ5MmRmMWE4NGZjMzQxOGU2ZWUwNWRiMWMyMDI3NTAwNWZmMA==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YjNiNDNhNjc0YTBkYzQzN2ViMzViNDY4M2YzODZmYWMyYzQ2ZTY2NWI2M2Yy
|
10
|
+
YTQ3NWE3OGRmNjA4YmIzNGNlMTY5NDgyMzJiYWQyNDQwYzE1MTIyMmY3MmM5
|
11
|
+
NzA3M2E0YjczMDk5N2Y2NzA1Mjg1OGU0MDE2M2NhODVjMDkxM2U=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGJjZDAyOTMzYWNjNzgwZmY2YzYzMWNkYTA3OTIxNWVhNDM0MDVhNjk0OGVk
|
14
|
+
MDU0YWJmY2FkMGI5MTdlZTcyM2ExMjBlNWQ1NjllYTI3MmQyN2IxZjc0OWM5
|
15
|
+
OTE2MGI3ZWU0MjkyNzAyZTQ0Y2M0ZWYyMzQzOWMyYWE0MjJlZWI=
|
data/.gitignore
ADDED
File without changes
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
nested_validator (1.0.0)
|
5
|
+
activemodel
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.0)
|
11
|
+
activesupport (= 4.1.0)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activesupport (4.1.0)
|
14
|
+
i18n (~> 0.6, >= 0.6.9)
|
15
|
+
json (~> 1.7, >= 1.7.7)
|
16
|
+
minitest (~> 5.1)
|
17
|
+
thread_safe (~> 0.1)
|
18
|
+
tzinfo (~> 1.1)
|
19
|
+
builder (3.2.2)
|
20
|
+
coveralls (0.7.2)
|
21
|
+
multi_json (~> 1.3)
|
22
|
+
rest-client (= 1.6.7)
|
23
|
+
simplecov (>= 0.7)
|
24
|
+
term-ansicolor (= 1.2.2)
|
25
|
+
thor (= 0.18.1)
|
26
|
+
diff-lcs (1.2.5)
|
27
|
+
docile (1.1.5)
|
28
|
+
i18n (0.6.11)
|
29
|
+
json (1.8.1)
|
30
|
+
mime-types (2.4.3)
|
31
|
+
minitest (5.3.3)
|
32
|
+
multi_json (1.10.1)
|
33
|
+
rake (10.4.2)
|
34
|
+
rest-client (1.6.7)
|
35
|
+
mime-types (>= 1.16)
|
36
|
+
rspec (3.1.0)
|
37
|
+
rspec-core (~> 3.1.0)
|
38
|
+
rspec-expectations (~> 3.1.0)
|
39
|
+
rspec-mocks (~> 3.1.0)
|
40
|
+
rspec-core (3.1.7)
|
41
|
+
rspec-support (~> 3.1.0)
|
42
|
+
rspec-expectations (3.1.2)
|
43
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
44
|
+
rspec-support (~> 3.1.0)
|
45
|
+
rspec-its (1.1.0)
|
46
|
+
rspec-core (>= 3.0.0)
|
47
|
+
rspec-expectations (>= 3.0.0)
|
48
|
+
rspec-mocks (3.1.3)
|
49
|
+
rspec-support (~> 3.1.0)
|
50
|
+
rspec-support (3.1.2)
|
51
|
+
simplecov (0.9.1)
|
52
|
+
docile (~> 1.1.0)
|
53
|
+
multi_json (~> 1.0)
|
54
|
+
simplecov-html (~> 0.8.0)
|
55
|
+
simplecov-html (0.8.0)
|
56
|
+
term-ansicolor (1.2.2)
|
57
|
+
tins (~> 0.8)
|
58
|
+
thor (0.18.1)
|
59
|
+
thread_safe (0.3.3)
|
60
|
+
tins (0.13.2)
|
61
|
+
tzinfo (1.1.0)
|
62
|
+
thread_safe (~> 0.1)
|
63
|
+
|
64
|
+
PLATFORMS
|
65
|
+
ruby
|
66
|
+
|
67
|
+
DEPENDENCIES
|
68
|
+
bundler (~> 1.6)
|
69
|
+
coveralls
|
70
|
+
nested_validator!
|
71
|
+
rake
|
72
|
+
rspec (~> 3.0)
|
73
|
+
rspec-its
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Eric Roberts
|
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,194 @@
|
|
1
|
+
[](http://badge.fury.io/rb/percentable)
|
2
|
+
[](https://travis-ci.org/ericroberts/percentable)
|
3
|
+
[](https://codeclimate.com/github/ericroberts/percentable)
|
4
|
+
[](https://coveralls.io/r/ericroberts/percentable?branch=master)
|
5
|
+
|
6
|
+
# Nested Validator
|
7
|
+
|
8
|
+
Nested validations allow a parent's class validity to include those of child
|
9
|
+
attributes. Errors messages will be copied from the child attribute to the parent.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'nested_validator'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install nested_validator
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
Assume we have a parent object and we want its validaty to depend on its child validity:
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
class ParentBase
|
31
|
+
include ActiveModel::Validations
|
32
|
+
|
33
|
+
attr_accessor :child
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
self.child = Child.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Child
|
41
|
+
include ActiveModel::Validations
|
42
|
+
|
43
|
+
attr_accessor :attribute1, :attribute2
|
44
|
+
|
45
|
+
validates :attribute1, presence: true
|
46
|
+
validates :attribute2, presence: true
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
Well, we can use a nested validation to do just that:
|
51
|
+
|
52
|
+
``` ruby
|
53
|
+
class Parent < ParentBase
|
54
|
+
validates :child, nested: true
|
55
|
+
end
|
56
|
+
|
57
|
+
parent = Parent.new
|
58
|
+
parent.valid?
|
59
|
+
puts parent.errors.messages
|
60
|
+
|
61
|
+
# => {:"child attribute1"=>["can't be blank"], :"child attribute2"=>["can't be blank"]}
|
62
|
+
```
|
63
|
+
### What if I want to validate with just some of the child attributes?
|
64
|
+
|
65
|
+
You can use an ```only``` option like this:
|
66
|
+
|
67
|
+
``` ruby
|
68
|
+
class ParentOnly < ParentBase
|
69
|
+
validates :child, nested: { only: :attribute1 }
|
70
|
+
end
|
71
|
+
|
72
|
+
parent = ParentOnly.new
|
73
|
+
parent.valid?
|
74
|
+
puts parent.errors.messages
|
75
|
+
|
76
|
+
# => {:"child attribute1"=>["can't be blank"]}
|
77
|
+
```
|
78
|
+
|
79
|
+
You can also provide an array of attributes to ```only``` if you want to include more than one.
|
80
|
+
|
81
|
+
### OK, is there a way to exclude some child attributes?
|
82
|
+
|
83
|
+
Sure thing. You can use the ```except``` option:
|
84
|
+
|
85
|
+
``` ruby
|
86
|
+
class ParentExcept < ParentBase
|
87
|
+
validates :child, nested: { except: :attribute2 }
|
88
|
+
end
|
89
|
+
|
90
|
+
parent = ParentExcept.new
|
91
|
+
parent.valid?
|
92
|
+
puts parent.errors.messages
|
93
|
+
|
94
|
+
# => {:"child attribute1"=>["can't be blank"]}
|
95
|
+
```
|
96
|
+
|
97
|
+
### Alright, what if I want a custom message?
|
98
|
+
|
99
|
+
You can specify a ```prefix``` instead of the child's attribute name:
|
100
|
+
|
101
|
+
``` ruby
|
102
|
+
class ParentPrefix < ParentBase
|
103
|
+
validates :child, nested: { only: :attribute1, prefix: 'OMG'}
|
104
|
+
end
|
105
|
+
|
106
|
+
parent = ParentPrefix.new
|
107
|
+
parent.valid?
|
108
|
+
puts parent.errors.messages
|
109
|
+
|
110
|
+
# => {:"OMG attribute1"=>["can't be blank"]}
|
111
|
+
```
|
112
|
+
|
113
|
+
### What happens if the child is an Array or Hash?
|
114
|
+
|
115
|
+
In this case, each value in the array or hash will be validated and the error message will
|
116
|
+
include the index or key of the value.
|
117
|
+
|
118
|
+
For an array:
|
119
|
+
|
120
|
+
``` ruby
|
121
|
+
class ParentArray < ParentBase
|
122
|
+
validates :child, nested: { only: :attribute1 }
|
123
|
+
end
|
124
|
+
|
125
|
+
parent = ParentArray.new
|
126
|
+
parent.child = [Child.new] * 2
|
127
|
+
parent.valid?
|
128
|
+
puts parent.errors.messages
|
129
|
+
|
130
|
+
# => {:"child[0] attribute1"=>["can't be blank"], :"child[1] attribute1"=>["can't be blank"]}
|
131
|
+
```
|
132
|
+
|
133
|
+
For a hash:
|
134
|
+
|
135
|
+
``` ruby
|
136
|
+
class ParentHash < ParentBase
|
137
|
+
validates :child, nested: { only: :attribute1 }
|
138
|
+
end
|
139
|
+
|
140
|
+
parent = ParentHash.new
|
141
|
+
parent.child = { thing1: Child.new, thing2: Child.new }
|
142
|
+
parent.valid?
|
143
|
+
puts parent.errors.messages
|
144
|
+
|
145
|
+
# => {:"child[thing1] attribute1"=>["can't be blank"], :"child[thing2] attribute1"=>["can't be blank"]}
|
146
|
+
```
|
147
|
+
|
148
|
+
### Can I easily use this for multiple child attributes?
|
149
|
+
|
150
|
+
You can use the ```validates_nested``` method:
|
151
|
+
|
152
|
+
``` ruby
|
153
|
+
class ParentMultiple < ParentBase
|
154
|
+
attr_accessor :child2
|
155
|
+
|
156
|
+
validates_nested :child, :child2, only: :attribute1
|
157
|
+
|
158
|
+
def initialize
|
159
|
+
self.child = Child.new
|
160
|
+
self.child2 = Child.new
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
parent = ParentMultiple.new
|
165
|
+
parent.valid?
|
166
|
+
puts parent.errors.messages
|
167
|
+
|
168
|
+
# => {:"child attribute1"=>["can't be blank"], :"child2 attribute1"=>["can't be blank"]}
|
169
|
+
```
|
170
|
+
|
171
|
+
## Testing With RSpec
|
172
|
+
|
173
|
+
When you ```require nested_validator``` you will have access to the RSpec matcher ```validate_nested```
|
174
|
+
that you can use in your specs.
|
175
|
+
|
176
|
+
Here are some examples:
|
177
|
+
|
178
|
+
``` ruby
|
179
|
+
describe Parent do
|
180
|
+
it { should validate_nested(:child) }
|
181
|
+
it { should validate_nested(:child).with_prefix(:thing1) }
|
182
|
+
it { should validate_nested(:child).only(:attribute1) }
|
183
|
+
it { should validate_nested(:child).only(:attribute1, :attribute2) }
|
184
|
+
it { should validate_nested(:child).except(:attribute1) }
|
185
|
+
it { should validate_nested(:child).except(:attribute1, :attribute2) }
|
186
|
+
end
|
187
|
+
```
|
188
|
+
## Contributing
|
189
|
+
|
190
|
+
1. Fork it ( https://github.com/dwhelan/nested_validator/fork )
|
191
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
192
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
193
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
194
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/core_ext/array'
|
3
|
+
|
4
|
+
module ActiveModel
|
5
|
+
module Validations
|
6
|
+
class NestedValidator < EachValidator
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def validate_each(record, attribute, values)
|
11
|
+
with_each_value(values) do |index, value|
|
12
|
+
prefix = prefix(attribute, index, include_index?(values))
|
13
|
+
record_error(record, prefix, value) if value.invalid?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_each_value(values, &block)
|
18
|
+
case values
|
19
|
+
when Hash
|
20
|
+
values.each { |key, value| block.call key, value }
|
21
|
+
else
|
22
|
+
Array.wrap(values).each_with_index { |value, index| block.call index, value}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def include_index?(values)
|
27
|
+
values.respond_to? :each
|
28
|
+
end
|
29
|
+
|
30
|
+
def prefix(attribute, index, include_index)
|
31
|
+
prefix = (options.has_key?(:prefix) ? options[:prefix] : attribute).to_s
|
32
|
+
prefix << "[#{index}]" if include_index
|
33
|
+
prefix
|
34
|
+
end
|
35
|
+
|
36
|
+
def record_error(record, prefix, value)
|
37
|
+
value.errors.each do |key, error|
|
38
|
+
record.errors.add(nested_key(prefix, key), error) if include?(key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def nested_key(prefix, key)
|
43
|
+
"#{prefix} #{key}".strip.to_sym
|
44
|
+
end
|
45
|
+
|
46
|
+
def include?(key)
|
47
|
+
if options[:only]
|
48
|
+
only.any?{|k| key =~ /^#{k}/}
|
49
|
+
elsif options[:except]
|
50
|
+
except.none?{|k| key =~ /^#{k}/}
|
51
|
+
else
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def only
|
57
|
+
@only ||= Array.wrap(options[:only])
|
58
|
+
end
|
59
|
+
|
60
|
+
def except
|
61
|
+
@except ||= Array.wrap(options[:except])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module HelperMethods
|
66
|
+
def validates_nested(*attributes)
|
67
|
+
validates_with NestedValidator, _merge_attributes(attributes)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# RSpec matcher to spec nested validations.
|
2
|
+
#
|
3
|
+
# You can use symbols or strings for any values.
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
#
|
7
|
+
# describe Parent do
|
8
|
+
# it { should validate_nested(:child) }
|
9
|
+
# it { should validate_nested(:child).with_prefix(:thing1) }
|
10
|
+
# it { should validate_nested(:child).only(:attribute1) }
|
11
|
+
# it { should validate_nested(:child).only(:attribute1, :attribute2) }
|
12
|
+
# it { should validate_nested(:child).except(:attribute1) }
|
13
|
+
# end
|
14
|
+
|
15
|
+
RSpec::Matchers.define :validate_nested do |child_name|
|
16
|
+
|
17
|
+
attr_accessor :child_name, :prefix, :only_keys, :except_keys # inputs
|
18
|
+
attr_accessor :parent, :actual_keys
|
19
|
+
|
20
|
+
TEST_KEY ||= :__test_key__
|
21
|
+
|
22
|
+
match do |parent|
|
23
|
+
self.prefix ||= ''
|
24
|
+
self.only_keys ||= []
|
25
|
+
self.except_keys ||= []
|
26
|
+
|
27
|
+
self.child_name = child_name
|
28
|
+
self.parent = parent
|
29
|
+
|
30
|
+
return false unless parent.respond_to? child_name
|
31
|
+
|
32
|
+
self.actual_keys = (error_keys_when_child_validity_is(false) - error_keys_when_child_validity_is(true))
|
33
|
+
|
34
|
+
actual_keys == expected_keys
|
35
|
+
end
|
36
|
+
|
37
|
+
chain(:with_prefix) { |prefix| self.prefix = prefix }
|
38
|
+
chain(:only) { |*only| self.only_keys = only }
|
39
|
+
chain(:except) { |*except| self.except_keys = except }
|
40
|
+
|
41
|
+
def child
|
42
|
+
parent.send child_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def error_keys_when_child_validity_is(valid)
|
46
|
+
child_error_keys = combine TEST_KEY, only_keys, except_keys
|
47
|
+
child_errors = child_error_keys.inject({}){|result, key| result[key] = ['error message'];result }
|
48
|
+
|
49
|
+
allow(child).to receive(:valid?) { valid }
|
50
|
+
allow(child).to receive(:errors) { valid ? [] : child_errors }
|
51
|
+
|
52
|
+
parent.valid?
|
53
|
+
parent.errors.keys
|
54
|
+
end
|
55
|
+
|
56
|
+
def expected_keys
|
57
|
+
expected_child_keys.map{|key| :"#{expected_prefix} #{key}"}
|
58
|
+
end
|
59
|
+
|
60
|
+
def expected_prefix
|
61
|
+
prefix.present? ? prefix : child_name
|
62
|
+
end
|
63
|
+
|
64
|
+
def actual_prefix
|
65
|
+
:"#{actual_keys.first.to_s.split.first}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def expected_child_keys
|
69
|
+
expected_keys = only_keys.present? ? only_keys : [TEST_KEY]
|
70
|
+
unique_except_keys = except_keys - only_keys
|
71
|
+
combine expected_keys - unique_except_keys
|
72
|
+
end
|
73
|
+
|
74
|
+
def actual_child_keys
|
75
|
+
actual_keys.map{|key| key.to_s.sub(/^.*\s+/, '').to_sym }
|
76
|
+
end
|
77
|
+
|
78
|
+
def invalid_child_keys
|
79
|
+
(only_keys + except_keys).reject{|key| child.respond_to? key}
|
80
|
+
end
|
81
|
+
|
82
|
+
description do
|
83
|
+
message = "validate nested #{show child_name}"
|
84
|
+
message << " with only: #{show only_keys}" if only_keys.present?
|
85
|
+
message << " except: #{show except_keys}" if except_keys.present?
|
86
|
+
message << " with prefix #{show prefix}" if prefix.present?
|
87
|
+
message
|
88
|
+
end
|
89
|
+
|
90
|
+
failure_message do
|
91
|
+
case
|
92
|
+
when !parent.respond_to?(child_name)
|
93
|
+
"#{parent} doesn't respond to #{show child_name}"
|
94
|
+
when invalid_child_keys.present?
|
95
|
+
"#{child_name} doesn't respond to #{show invalid_child_keys}"
|
96
|
+
when (missing_child_keys = expected_child_keys - actual_child_keys - invalid_child_keys - [TEST_KEY]).present?
|
97
|
+
"#{parent} doesn't nest validations for: #{show missing_child_keys}"
|
98
|
+
when actual_keys.empty?
|
99
|
+
"parent doesn't nest validations for #{show child_name}"
|
100
|
+
when actual_prefix != expected_prefix
|
101
|
+
if prefix.present?
|
102
|
+
"parent uses a prefix of #{show actual_prefix} rather than #{show expected_prefix}"
|
103
|
+
else
|
104
|
+
"parent has a prefix of #{show actual_prefix}. Are you missing '.with_prefix(#{show actual_prefix})'?"
|
105
|
+
end
|
106
|
+
else
|
107
|
+
"parent does nest validations for: #{show except_keys & actual_child_keys}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
failure_message_when_negated do
|
112
|
+
case
|
113
|
+
when !parent.respond_to?(child_name)
|
114
|
+
"#{parent} doesn't respond to #{show child_name}"
|
115
|
+
when (extras = only_keys & actual_child_keys).present?
|
116
|
+
"#{parent} does nest #{show child_name} validations for: #{show extras}"
|
117
|
+
when invalid_child_keys.present?
|
118
|
+
"#{child_name} doesn't respond to #{show invalid_child_keys}"
|
119
|
+
when except_keys.present?
|
120
|
+
"#{parent} doesn't nest #{show child_name} validations for: #{show except_keys - actual_child_keys}"
|
121
|
+
when prefix.present?
|
122
|
+
"#{parent} does nest validations for: #{show child_name} with a prefix of #{show prefix}"
|
123
|
+
else
|
124
|
+
"#{parent} does nest validations for: #{show child_name}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def show(value)
|
129
|
+
Array.wrap(value).map{|key| key.is_a?(Symbol) ? ":#{key}" : key.to_s}.join(', ')
|
130
|
+
end
|
131
|
+
|
132
|
+
def combine(*keys)
|
133
|
+
keys.flatten.compact
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'nested_validator/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'nested_validator'
|
9
|
+
spec.version = NestedValidator::VERSION
|
10
|
+
spec.authors = ['Declan Whelan']
|
11
|
+
spec.email = ['declanpwhelan@gmail.com']
|
12
|
+
spec.summary = 'A validator that supports nesting.'
|
13
|
+
spec.description = "Nested validations allow a parent's class validity to include those of child attributes. Errors messages will be copied from the child attribute to the parent."
|
14
|
+
spec.homepage = 'https://github.com/dwhelan/nested_validator'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'activemodel'
|
23
|
+
|
24
|
+
#spec.add_development_dependency 'awesome_print'
|
25
|
+
#spec.add_development_dependency 'pry'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
30
|
+
spec.add_development_dependency 'rspec-its'
|
31
|
+
spec.add_development_dependency 'coveralls'
|
32
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nested_validator'
|
3
|
+
|
4
|
+
describe NestedValidator do
|
5
|
+
let(:base_class) do
|
6
|
+
Class.new {
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
# To keep ActiveModel happy
|
10
|
+
def self.model_name
|
11
|
+
ActiveModel::Name.new(self, nil, 'temp')
|
12
|
+
end
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:parent_class) do
|
17
|
+
Class.new(base_class) {
|
18
|
+
attr_accessor :child1, :child2
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:child_class) do
|
23
|
+
Class.new(base_class) {
|
24
|
+
attr_accessor :attribute1
|
25
|
+
validates :attribute1, presence: true
|
26
|
+
|
27
|
+
attr_accessor :attribute2
|
28
|
+
validates :attribute2, presence: true
|
29
|
+
|
30
|
+
attr_accessor :attribute3
|
31
|
+
validates :attribute3, presence: true
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@attribute1 = 'valid'
|
35
|
+
@attribute2 = 'valid'
|
36
|
+
@attribute3 = 'valid'
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:child1) { child_class.new }
|
42
|
+
let(:child2) { child_class.new }
|
43
|
+
|
44
|
+
def parent_with(&block)
|
45
|
+
parent = Class.new(parent_class) { instance_exec &block }.new
|
46
|
+
parent.child1 = child1
|
47
|
+
parent.child2 = child2
|
48
|
+
parent
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_nested_options(options)
|
52
|
+
parent_with { validates :child1, nested: options }
|
53
|
+
end
|
54
|
+
|
55
|
+
shared_examples 'valid:' do |child_name, *attributes|
|
56
|
+
attributes.each do |attribute|
|
57
|
+
specify "when #{child_name}.#{attribute} set to nil" do
|
58
|
+
send(child_name).send("#{attribute}=", nil)
|
59
|
+
expect(subject).to be_valid
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
shared_examples 'invalid:' do |child_name, *attributes|
|
65
|
+
attributes.each do |attribute|
|
66
|
+
specify "when #{child_name}.#{attribute} set to nil" do
|
67
|
+
send(child_name).send("#{attribute}=", nil)
|
68
|
+
expect(subject).to be_invalid
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'with "nested: true"' do
|
74
|
+
subject { with_nested_options true }
|
75
|
+
|
76
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1, :attribute2, :attribute3
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'error messages' do
|
80
|
+
context 'with scalar values' do
|
81
|
+
before { child1.attribute1 = nil;subject.valid? }
|
82
|
+
|
83
|
+
describe 'with no prefix' do
|
84
|
+
subject { with_nested_options true }
|
85
|
+
|
86
|
+
its('errors.messages') { should eq :'child1 attribute1' => ["can't be blank"] }
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'with "prefix: "OMG""' do
|
90
|
+
subject { with_nested_options prefix: 'OMG' }
|
91
|
+
|
92
|
+
its('errors.messages') { should eq :'OMG attribute1' => ["can't be blank"] }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'with an array of values' do
|
97
|
+
let(:child1) { [child_class.new, child_class.new] }
|
98
|
+
|
99
|
+
before { child1[0].attribute1 = nil;subject.valid? }
|
100
|
+
subject { with_nested_options true }
|
101
|
+
|
102
|
+
describe 'with single invalid value' do
|
103
|
+
its('errors.messages') { should eq :'child1[0] attribute1' => ["can't be blank"] }
|
104
|
+
end
|
105
|
+
|
106
|
+
describe 'with multiple invalid values' do
|
107
|
+
before { child1[1].attribute1 = nil;subject.valid? }
|
108
|
+
its('errors.messages') { should include :'child1[0] attribute1' => ["can't be blank"] }
|
109
|
+
its('errors.messages') { should include :'child1[1] attribute1' => ["can't be blank"] }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with a prefix' do
|
113
|
+
subject { with_nested_options prefix: 'OMG' }
|
114
|
+
|
115
|
+
its('errors.messages') { should eq :'OMG[0] attribute1' => ["can't be blank"] }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'with a hash of values' do
|
121
|
+
let(:child1) { { first: child_class.new, second: child_class.new } }
|
122
|
+
|
123
|
+
before { child1[:first].attribute1 = nil;subject.valid? }
|
124
|
+
subject { with_nested_options true }
|
125
|
+
|
126
|
+
describe 'with single invalid value' do
|
127
|
+
its('errors.messages') { should eq :'child1[first] attribute1' => ["can't be blank"] }
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'with multiple invalid values' do
|
131
|
+
before { child1[:second].attribute1 = nil;subject.valid? }
|
132
|
+
its('errors.messages') { should include :'child1[first] attribute1' => ["can't be blank"] }
|
133
|
+
its('errors.messages') { should include :'child1[second] attribute1' => ["can't be blank"] }
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with a prefix' do
|
137
|
+
subject { with_nested_options prefix: 'OMG' }
|
138
|
+
|
139
|
+
its('errors.messages') { should eq :'OMG[first] attribute1' => ["can't be blank"] }
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '"validates :child1, nested: {only: ...}"' do
|
144
|
+
|
145
|
+
describe 'with "only: :attribute1"' do
|
146
|
+
|
147
|
+
subject { with_nested_options only: :attribute1 }
|
148
|
+
|
149
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1
|
150
|
+
it_should_validate_nested 'valid:', :child1, :attribute2, :attribute3
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'with "only: [:atnested_validator_spec.rbtribute1, :attribute2]"' do
|
154
|
+
|
155
|
+
subject { parent_with { validates :child1, nested: { only: [:attribute1, :attribute2] } } }
|
156
|
+
|
157
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1, :attribute2
|
158
|
+
it_should_validate_nested 'valid:', :child1, :attribute3
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
describe '"validates :child1, nested: {except: ...}"' do
|
163
|
+
describe 'with "except: :attribute1"' do
|
164
|
+
subject { with_nested_options except: :attribute1 }
|
165
|
+
|
166
|
+
it_should_validate_nested 'invalid:', :child1, :attribute2, :attribute3
|
167
|
+
it_should_validate_nested 'valid:', :child1, :attribute1
|
168
|
+
end
|
169
|
+
|
170
|
+
describe 'with "except: [:attribute1, :attribute2"' do
|
171
|
+
subject { with_nested_options except: [:attribute1, :attribute2] }
|
172
|
+
|
173
|
+
it_should_validate_nested 'invalid:', :child1, :attribute3
|
174
|
+
it_should_validate_nested 'valid:', :child1, :attribute1, :attribute2
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'attributes in "only" option should take precedence over "except"' do
|
179
|
+
describe '"validates :child1, nested: {only: attribute1, except: :attribute1}"' do
|
180
|
+
subject { with_nested_options only: :attribute1, except: :attribute1 }
|
181
|
+
|
182
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1
|
183
|
+
it_should_validate_nested 'valid:', :child1, :attribute2, :attribute3
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'validates_nested' do
|
188
|
+
|
189
|
+
describe 'validates_nested :child1' do
|
190
|
+
subject { parent_with { validates_nested :child1 } }
|
191
|
+
|
192
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1, :attribute2, :attribute3
|
193
|
+
it_should_validate_nested 'valid:', :child2, :attribute1, :attribute2, :attribute3
|
194
|
+
end
|
195
|
+
|
196
|
+
describe 'validates_nested :child1, :child2' do
|
197
|
+
subject { parent_with { validates_nested :child1, :child2 } }
|
198
|
+
|
199
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1, :attribute2, :attribute3
|
200
|
+
it_should_validate_nested 'invalid:', :child2, :attribute1, :attribute2, :attribute3
|
201
|
+
end
|
202
|
+
|
203
|
+
describe 'validates_nested :child1, :child2, only: :attribute1' do
|
204
|
+
subject { parent_with { validates_nested :child1, :child2, only: :attribute1 } }
|
205
|
+
|
206
|
+
it_should_validate_nested 'invalid:', :child1, :attribute1
|
207
|
+
it_should_validate_nested 'invalid:', :child2, :attribute1
|
208
|
+
it_should_validate_nested 'valid:', :child1, :attribute2, :attribute3
|
209
|
+
it_should_validate_nested 'valid:', :child2, :attribute2, :attribute3
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nested_validator'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'Typical usage' do
|
6
|
+
class Child
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
attr_accessor :attribute1, :attribute2
|
10
|
+
validates :attribute1, presence: true
|
11
|
+
validates :attribute2, presence: true
|
12
|
+
end
|
13
|
+
|
14
|
+
class ParentBase
|
15
|
+
include ActiveModel::Validations
|
16
|
+
|
17
|
+
attr_accessor :child
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
self.child = Child.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
specify 'simple example' do
|
25
|
+
class Parent < ParentBase
|
26
|
+
validates :child, nested: true
|
27
|
+
end
|
28
|
+
|
29
|
+
parent = Parent.new
|
30
|
+
parent.valid?
|
31
|
+
puts parent.errors.messages
|
32
|
+
end
|
33
|
+
|
34
|
+
specify 'only certain child attributes' do
|
35
|
+
class ParentOnly < ParentBase
|
36
|
+
validates :child, nested: { only: :attribute1 }
|
37
|
+
end
|
38
|
+
|
39
|
+
parent = ParentOnly.new
|
40
|
+
parent.valid?
|
41
|
+
puts parent.errors.messages
|
42
|
+
end
|
43
|
+
|
44
|
+
specify 'except certain child attributes' do
|
45
|
+
class ParentExcept < ParentBase
|
46
|
+
validates :child, nested: { except: :attribute2 }
|
47
|
+
end
|
48
|
+
|
49
|
+
parent = ParentExcept.new
|
50
|
+
parent.valid?
|
51
|
+
puts parent.errors.messages
|
52
|
+
end
|
53
|
+
|
54
|
+
specify 'custom message prefix' do
|
55
|
+
class ParentPrefix < ParentBase
|
56
|
+
validates :child, nested: { only: :attribute1, prefix: 'OMG'}
|
57
|
+
end
|
58
|
+
|
59
|
+
parent = ParentPrefix.new
|
60
|
+
parent.valid?
|
61
|
+
puts parent.errors.messages
|
62
|
+
end
|
63
|
+
|
64
|
+
specify 'array of values' do
|
65
|
+
class ParentArray < ParentBase
|
66
|
+
validates :child, nested: { only: :attribute1 }
|
67
|
+
end
|
68
|
+
|
69
|
+
parent = ParentArray.new
|
70
|
+
parent.child = [Child.new] * 2
|
71
|
+
parent.valid?
|
72
|
+
puts parent.errors.messages
|
73
|
+
end
|
74
|
+
|
75
|
+
specify 'hash of values' do
|
76
|
+
class ParentHash < ParentBase
|
77
|
+
validates :child, nested: { only: :attribute1 }
|
78
|
+
end
|
79
|
+
|
80
|
+
parent = ParentHash.new
|
81
|
+
parent.child = { thing1: Child.new, thing2: Child.new }
|
82
|
+
parent.valid?
|
83
|
+
puts parent.errors.messages
|
84
|
+
end
|
85
|
+
|
86
|
+
specify 'multiple children' do
|
87
|
+
class ParentMultiple < ParentBase
|
88
|
+
attr_accessor :child2
|
89
|
+
|
90
|
+
validates_nested :child, :child2, only: :attribute1
|
91
|
+
|
92
|
+
def initialize
|
93
|
+
self.child = Child.new
|
94
|
+
self.child2 = Child.new
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
parent = ParentMultiple.new
|
99
|
+
parent.valid?
|
100
|
+
puts parent.errors.messages
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'nested_validator'
|
3
|
+
|
4
|
+
describe 'validates_nested with [parent class with "validates, child1]"' do
|
5
|
+
let(:parent_class) do
|
6
|
+
Class.new {
|
7
|
+
include ActiveModel::Validations
|
8
|
+
|
9
|
+
attr_accessor :child1, :child2
|
10
|
+
|
11
|
+
def initialize(child1=nil, child2=nil)
|
12
|
+
self.child1 = child1
|
13
|
+
self.child2 = child2
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
'parent'
|
18
|
+
end
|
19
|
+
}
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:child_class) do
|
24
|
+
Class.new {
|
25
|
+
include ActiveModel::Validations
|
26
|
+
|
27
|
+
attr_accessor :attribute1, :attribute2, :attribute3
|
28
|
+
|
29
|
+
validates :attribute1, presence: true
|
30
|
+
validates :attribute2, presence: true
|
31
|
+
validates :attribute3, presence: true
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@attribute1 = 'valid'
|
35
|
+
@attribute2 = 'valid'
|
36
|
+
@attribute3 = 'valid'
|
37
|
+
end
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:parent) { parent_class.new child_class.new, child_class.new }
|
42
|
+
let(:options) { { presence: true } }
|
43
|
+
|
44
|
+
before { parent_class.class_eval "validates :child1, #{options}" }
|
45
|
+
|
46
|
+
describe 'its validations with options:' do
|
47
|
+
let(:options) { self.class.description }
|
48
|
+
|
49
|
+
subject { parent }
|
50
|
+
|
51
|
+
context 'nested: true' do
|
52
|
+
it { should validate_nested(:child1) }
|
53
|
+
it { should validate_nested('child1') }
|
54
|
+
|
55
|
+
it { should_not validate_nested(:child2) }
|
56
|
+
it { should_not validate_nested(:invalid_child_name) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'nested: {prefix: "OMG"}' do
|
60
|
+
|
61
|
+
it { should validate_nested(:child1).with_prefix('OMG') }
|
62
|
+
it { should validate_nested(:child1).with_prefix(:OMG) }
|
63
|
+
|
64
|
+
it { should_not validate_nested(:child1) }
|
65
|
+
it { should_not validate_nested(:child1).with_prefix('WTF') }
|
66
|
+
it { should_not validate_nested(:child1).with_prefix(:WTF) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'nested: {only: :attribute1}' do
|
70
|
+
it { should validate_nested(:child1).only(:attribute1) }
|
71
|
+
it { should validate_nested(:child1).only('attribute1') }
|
72
|
+
|
73
|
+
it { should_not validate_nested(:child1).only(:attribute2) }
|
74
|
+
it { should_not validate_nested(:child1).only('attribute2') }
|
75
|
+
it { should_not validate_nested(:child1).only(:invalid_attribute_name) }
|
76
|
+
it { should_not validate_nested(:child1).only(:attribute1, :attribute2) }
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'nested: {only: [:attribute1, :attribute2]}' do
|
80
|
+
it { should validate_nested(:child1).only(:attribute1) }
|
81
|
+
it { should validate_nested(:child1).only(:attribute2) }
|
82
|
+
it { should validate_nested(:child1).only(:attribute1, :attribute2) }
|
83
|
+
it { should validate_nested(:child1).only('attribute1', 'attribute2') }
|
84
|
+
|
85
|
+
it { should_not validate_nested(:child1).only(:attribute2, :attribute3) }
|
86
|
+
it { should_not validate_nested(:child1).only(:invalid_attribute_name) }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'nested: {except: :attribute1}' do
|
90
|
+
it { should validate_nested(:child1).except(:attribute1) }
|
91
|
+
it { should validate_nested(:child1).except('attribute1') }
|
92
|
+
|
93
|
+
it { should_not validate_nested(:child1).except(:attribute2) }
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'nested: {except: [:attribute1, :attribute2]}' do
|
97
|
+
it { should validate_nested(:child1).except(:attribute1, :attribute2) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'its description for:' do
|
102
|
+
let(:validator) { instance_eval self.class.description }
|
103
|
+
|
104
|
+
subject { validator.description }
|
105
|
+
|
106
|
+
context('validate_nested(:child1)') { it { should eq 'validate nested :child1' } }
|
107
|
+
context('validate_nested(:child1).only(:attribute1)') { it { should eq 'validate nested :child1 with only: :attribute1' } }
|
108
|
+
context('validate_nested(:child1).except(:attribute1)') { it { should eq 'validate nested :child1 except: :attribute1' } }
|
109
|
+
context('validate_nested(:child1).with_prefix(:OMG)') { it { should eq 'validate nested :child1 with prefix :OMG' } }
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'its error messages:' do
|
113
|
+
let(:options) { self.class.parent.description }
|
114
|
+
let(:validator) { instance_eval self.class.description }
|
115
|
+
|
116
|
+
describe 'should failure messages for' do
|
117
|
+
before { expect(validator.matches? parent).to be false }
|
118
|
+
|
119
|
+
subject { validator.failure_message }
|
120
|
+
|
121
|
+
context 'nested: true' do
|
122
|
+
describe('validate_nested(:child2)') { it { should eq "parent doesn't nest validations for :child2" } }
|
123
|
+
describe('validate_nested(:invalid_child_name)') { it { should eq "parent doesn't respond to :invalid_child_name" } }
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'nested: {prefix: :OMG}' do
|
127
|
+
describe('validate_nested(:child1)') { it { should eq "parent has a prefix of :OMG. Are you missing '.with_prefix(:OMG)'?" } }
|
128
|
+
describe('validate_nested(:child1).with_prefix(:WTF)') { it { should eq 'parent uses a prefix of :OMG rather than :WTF' } }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'nested: {only: :attribute1}' do
|
132
|
+
describe('validate_nested(:child1).only(:invalid_attribute_name)') { it { should eq "child1 doesn't respond to :invalid_attribute_name" } }
|
133
|
+
describe('validate_nested(:child1).only(:attribute2)') { it { should eq "parent doesn't nest validations for: :attribute2" } }
|
134
|
+
describe('validate_nested(:child1).only(:attribute1, :attribute2)') { it { should eq "parent doesn't nest validations for: :attribute2" } }
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'nested: {except: :attribute1}' do
|
138
|
+
describe('validate_nested(:child1).except(:invalid_attribute_name)') { it { should eq "child1 doesn't respond to :invalid_attribute_name" } }
|
139
|
+
describe('validate_nested(:child1).except(:attribute2)') { it { should eq 'parent does nest validations for: :attribute2' } }
|
140
|
+
describe('validate_nested(:child1).except(:attribute1, :attribute2)') { it { should eq 'parent does nest validations for: :attribute2' } }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'should_not failure messages for' do
|
145
|
+
before { expect(validator.matches? parent).to be true }
|
146
|
+
|
147
|
+
subject { validator.failure_message_when_negated }
|
148
|
+
|
149
|
+
context 'nested: true' do
|
150
|
+
describe('validate_nested(:child1)') { it { should eq 'parent does nest validations for: :child1' } }
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'nested: {prefix: :OMG}' do
|
154
|
+
describe('validate_nested(:child1).with_prefix(:OMG)') { it { should eq 'parent does nest validations for: :child1 with a prefix of :OMG' } }
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'nested: {only: :attribute1}' do
|
158
|
+
describe('validate_nested(:child1).only(:attribute1)') { it { should eq 'parent does nest :child1 validations for: :attribute1' } }
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'nested: {only: [:attribute1, :attribute2]}' do
|
162
|
+
describe('validate_nested(:child1).only(:attribute1)') { it { should eq 'parent does nest :child1 validations for: :attribute1' } }
|
163
|
+
describe('validate_nested(:child1).only(:attribute1, :attribute2)') { it { should eq 'parent does nest :child1 validations for: :attribute1, :attribute2' } }
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'nested: {except: :attribute1}' do
|
167
|
+
describe('validate_nested(:child1).except(:attribute1)') { it { should eq "parent doesn't nest :child1 validations for: :attribute1" } }
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'nested: {except: [:attribute1, :attribute2]}' do
|
171
|
+
describe('validate_nested(:child1).except(:attribute1)') { it { should eq "parent doesn't nest :child1 validations for: :attribute1" } }
|
172
|
+
describe('validate_nested(:child1).except(:attribute1, :attribute2)') { it { should eq "parent doesn't nest :child1 validations for: :attribute1, :attribute2" } }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'rspec/its'
|
3
|
+
require 'coveralls'
|
4
|
+
|
5
|
+
#require 'pry'
|
6
|
+
#require 'awesome_print'
|
7
|
+
|
8
|
+
I18n.enforce_available_locales = true
|
9
|
+
Coveralls.wear!
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.filter_run focus: true
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
|
15
|
+
config.alias_it_should_behave_like_to :it_should_validate_nested, 'the parent object should be'
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nested_validator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Declan Whelan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-its
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Nested validations allow a parent's class validity to include those of
|
98
|
+
child attributes. Errors messages will be copied from the child attribute to the
|
99
|
+
parent.
|
100
|
+
email:
|
101
|
+
- declanpwhelan@gmail.com
|
102
|
+
executables: []
|
103
|
+
extensions: []
|
104
|
+
extra_rdoc_files: []
|
105
|
+
files:
|
106
|
+
- .gitignore
|
107
|
+
- .rspec
|
108
|
+
- .travis.yml
|
109
|
+
- Gemfile
|
110
|
+
- Gemfile.lock
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- lib/nested_validator.rb
|
115
|
+
- lib/nested_validator/nested_validator.rb
|
116
|
+
- lib/nested_validator/validate_nested_matcher.rb
|
117
|
+
- lib/nested_validator/version.rb
|
118
|
+
- nested_validator.gemspec
|
119
|
+
- spec/lib/nested_validator/nested_validator_spec.rb
|
120
|
+
- spec/lib/nested_validator/usage_spec.rb
|
121
|
+
- spec/lib/nested_validator/validate_nested_matcher_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
homepage: https://github.com/dwhelan/nested_validator
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.1.10
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: A validator that supports nesting.
|
147
|
+
test_files:
|
148
|
+
- spec/lib/nested_validator/nested_validator_spec.rb
|
149
|
+
- spec/lib/nested_validator/usage_spec.rb
|
150
|
+
- spec/lib/nested_validator/validate_nested_matcher_spec.rb
|
151
|
+
- spec/spec_helper.rb
|