validates_subset 1.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6ac3e5de7a80d20cdddb2d41527f9228a767cc5f
4
+ data.tar.gz: 7fbf538b71f54145550823de261b4ac6fb356b7e
5
+ SHA512:
6
+ metadata.gz: 4f8b260d9e133cd883f4bede331137c57aa51a68ea7883a026e5c6c5306cc2c822584b6c2d4870cb02f0027f1d6fba8d739025e6ba3592a8effc62751ebf5ccb
7
+ data.tar.gz: aec647c12952a02a976d09fd4c84c4c2b6629b66a40db0dd45eea4d74cd23fa6b616b59c8005680ebfb7ce5949893cc5777623a6310739b05f969b4511e753aa
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .ruby-*
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: 2.2.1
3
+ script: bundle exec rspec spec
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activemodel'
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'pry'
8
+ gem 'guard-rspec'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,84 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activemodel (4.2.1)
5
+ activesupport (= 4.2.1)
6
+ builder (~> 3.1)
7
+ activesupport (4.2.1)
8
+ i18n (~> 0.7)
9
+ json (~> 1.7, >= 1.7.7)
10
+ minitest (~> 5.1)
11
+ thread_safe (~> 0.3, >= 0.3.4)
12
+ tzinfo (~> 1.1)
13
+ builder (3.2.2)
14
+ celluloid (0.16.0)
15
+ timers (~> 4.0.0)
16
+ coderay (1.1.0)
17
+ diff-lcs (1.2.5)
18
+ ffi (1.9.8)
19
+ formatador (0.2.5)
20
+ guard (2.12.5)
21
+ formatador (>= 0.2.4)
22
+ listen (~> 2.7)
23
+ lumberjack (~> 1.0)
24
+ nenv (~> 0.1)
25
+ notiffany (~> 0.0)
26
+ pry (>= 0.9.12)
27
+ shellany (~> 0.0)
28
+ thor (>= 0.18.1)
29
+ guard-compat (1.2.1)
30
+ guard-rspec (4.5.2)
31
+ guard (~> 2.1)
32
+ guard-compat (~> 1.1)
33
+ rspec (>= 2.99.0, < 4.0)
34
+ hitimes (1.2.2)
35
+ i18n (0.7.0)
36
+ json (1.8.3)
37
+ listen (2.10.0)
38
+ celluloid (~> 0.16.0)
39
+ rb-fsevent (>= 0.9.3)
40
+ rb-inotify (>= 0.9)
41
+ lumberjack (1.0.9)
42
+ method_source (0.8.2)
43
+ minitest (5.7.0)
44
+ nenv (0.2.0)
45
+ notiffany (0.0.6)
46
+ nenv (~> 0.1)
47
+ shellany (~> 0.0)
48
+ pry (0.10.1)
49
+ coderay (~> 1.1.0)
50
+ method_source (~> 0.8.1)
51
+ slop (~> 3.4)
52
+ rb-fsevent (0.9.5)
53
+ rb-inotify (0.9.5)
54
+ ffi (>= 0.5.0)
55
+ rspec (3.2.0)
56
+ rspec-core (~> 3.2.0)
57
+ rspec-expectations (~> 3.2.0)
58
+ rspec-mocks (~> 3.2.0)
59
+ rspec-core (3.2.3)
60
+ rspec-support (~> 3.2.0)
61
+ rspec-expectations (3.2.1)
62
+ diff-lcs (>= 1.2.0, < 2.0)
63
+ rspec-support (~> 3.2.0)
64
+ rspec-mocks (3.2.1)
65
+ diff-lcs (>= 1.2.0, < 2.0)
66
+ rspec-support (~> 3.2.0)
67
+ rspec-support (3.2.2)
68
+ shellany (0.0.1)
69
+ slop (3.6.0)
70
+ thor (0.19.1)
71
+ thread_safe (0.3.5)
72
+ timers (4.0.1)
73
+ hitimes
74
+ tzinfo (1.2.2)
75
+ thread_safe (~> 0.1)
76
+
77
+ PLATFORMS
78
+ ruby
79
+
80
+ DEPENDENCIES
81
+ activemodel
82
+ guard-rspec
83
+ pry
84
+ rspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'rspec', notification: false do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ ## validates_subset
2
+
3
+ ![Build Status](https://travis-ci.org/yez/validates_subset.svg?branch=master)
4
+
5
+ ### Subset validator for Rails
6
+
7
+ #### Purpose
8
+
9
+ Validate that an attribute is a subset of another. If an attribute saved to the database (or simply kept in memory) needs to be a subset of another, this is the way to go.
10
+
11
+ #### Usage
12
+
13
+ ##### With ActiveRecord
14
+
15
+ ```ruby
16
+ class Foo < ActiveRecord::Base
17
+ attr_accessor :bar
18
+
19
+ # validate that the attribute :bar is a subset of the set [2, 4, 5]
20
+ validates_subset :bar, [2, 4, 5]
21
+ end
22
+
23
+ # Example usage:
24
+ foo = Foo.new(bar: [2])
25
+ foo.valid?
26
+ # => true
27
+
28
+ foo = Foo.new(bar: [7])
29
+ foo.valid?
30
+ # => false
31
+
32
+ foo = Foo.new(bar: 'banana')
33
+ foo.valid?
34
+ # => false
35
+ ```
36
+
37
+ ##### With ActiveModel
38
+
39
+ ```ruby
40
+ class Bar
41
+ include ActiveModel
42
+
43
+ attr_accessor :foo
44
+
45
+ # validate that the attribute :foo is a subset of the set ['a', 'b', 'c']
46
+ validates_subset :foo, ['a', 'b', 'c']
47
+ end
48
+
49
+ # Example usage:
50
+ bar = Bar.new(foo: ['a'])
51
+ bar.valid?
52
+ # => true
53
+
54
+ bar = Bar.new(foo: ['q'])
55
+ bar.valid?
56
+ # => false
57
+
58
+ bar = Bar.new(foo: 123)
59
+ bar.valid?
60
+ # => false
61
+ ```
62
+
63
+ ##### With `validates` syntax
64
+
65
+ ```ruby
66
+ class Banana < ActiveRecord::Base
67
+
68
+ attr_accessor :peel
69
+
70
+ # validate that the attribute :peel is a subset of the set ['yellow', 'green']
71
+ validates :peel, subset: ['yellow', 'green']
72
+ end
73
+
74
+ # Example usage:
75
+ banana = Banana.new(peel: ['yellow'])
76
+ banana.valid?
77
+ # => true
78
+
79
+ banana = Banana.new(peel: ['brown']) # Who wants a brown peel? No one.
80
+ banana.valid?
81
+ # => false
82
+
83
+ banana = Banana.new(peel: 123)
84
+ banana.valid?
85
+ # => false
86
+ ```
87
+
88
+ ##### With multiple modifiers
89
+
90
+ ```ruby
91
+ class Foo < ActiveRecord::Base
92
+ attr_accessor :baz
93
+
94
+ # validate that attribute :baz is a subset of [1, 2, 3] with a custom error message
95
+ # only if :conditional_method evaluates to true
96
+ validates_subset :baz, [1, 2, 3],
97
+ message: 'Baz is not a subset of [1, 2, 3], make it so!',
98
+ if: :conditional_method
99
+
100
+ def conditional_method
101
+ # some kind of logic that is important to pass
102
+ end
103
+ end
104
+
105
+ # Example usage:
106
+ foo = Foo.new(baz: [1])
107
+
108
+ foo.valid?
109
+ # => true
110
+
111
+ foo = Foo.new(baz: [99999])
112
+
113
+ # When the conditional method is true
114
+ foo.conditional_method
115
+ # => true
116
+ foo.valid?
117
+ # => false
118
+
119
+ # When the conditional method is false
120
+ foo.conditional_method
121
+ # => false
122
+ foo.valid?
123
+ # => true
124
+ ```
data/lib/arguments.rb ADDED
@@ -0,0 +1,44 @@
1
+ module ValidatesSubset
2
+ # Wrapper class for arguments consumed by validates_with
3
+ class Arguments
4
+ # @initialize
5
+ # param: attribute_name <Symbol> - Name of attribute that will be validated
6
+ # param: superset <Array> - Superset of attributes to validate set against
7
+ # param: options <Hash> - Extra options to pass along to the validator
8
+ # i.e. allow_nil: true, message: 'my custom message'
9
+ # return: nil
10
+ def initialize(attribute_name, superset, options)
11
+ @attribute_name = attribute_name
12
+ @superset = superset
13
+ @options = options.is_a?(Hash) ? options : {}
14
+ end
15
+
16
+ # format expected by _merge_attributes
17
+ #
18
+ # @to_validation_attributes
19
+ # return: <Array> - cardinality of 2
20
+ def to_validation_attributes
21
+ [@attribute_name, merged_options]
22
+ end
23
+
24
+ private
25
+
26
+ # helper method to compact all the options together along
27
+ # with the subset for validation
28
+ #
29
+ # @merged_options
30
+ # return: <Hash>
31
+ def merged_options
32
+ subset.merge(@options)
33
+ end
34
+
35
+ # helper method to impose the subset for validation into an option
36
+ # that will be merged later
37
+ #
38
+ # @subset
39
+ # return: <Hash>
40
+ def subset
41
+ { :subset => @superset }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,98 @@
1
+ require 'active_model'
2
+
3
+ require_relative './arguments'
4
+
5
+ module ActiveModel
6
+ module Validations
7
+ class SubsetValidator < ActiveModel::EachValidator
8
+ # @initialize
9
+ # param: options <Hash> - options hash of how to validate this attribute
10
+ # including custom messaging due to failures, specifying
11
+ # the subset the attribute to validate against, etc.
12
+ # return: result of ActiveModel::Validations::EachValidator initialize
13
+ def initialize(options)
14
+ options[:superset] = options.delete(:subset)
15
+
16
+ merged_options = {
17
+ :message => "is expected to be a subset of #{ options[:superset] } and is not."
18
+ }.merge(options)
19
+
20
+ super(merged_options)
21
+ end
22
+
23
+ # Validate that the array is a subset of what we expect
24
+ #
25
+ # @validate_each
26
+ # param: record <Object> - subject containing attribute to validate
27
+ # param: attribute <Symbol> - name of attribute to validate
28
+ # param: value <Variable> - value of attribute to validate
29
+ # return: nil
30
+ def validate_each(record, attribute, value)
31
+ add_errors_or_raise(options, record, attribute) unless is_subset?(value, options[:superset])
32
+ end
33
+
34
+ private
35
+
36
+ def is_subset?(set, superset)
37
+ set.is_a?(Array) && (set & superset) == set
38
+ end
39
+
40
+ # Helper method to either add messages to the errors object
41
+ # or raise an exception in :strict mode
42
+ #
43
+ # @add_errors_or_raise
44
+ # param: options <Hash> - options hash with strict flag or class
45
+ # param: record <Object> - subject containg attribute to validate
46
+ # param: attribute <Symbol> - name of attribute under validation
47
+ # return: nil
48
+ def add_errors_or_raise(options, record, attribute)
49
+ error = options_error(options[:strict])
50
+
51
+ raise error unless error.nil?
52
+
53
+ record.errors.add(attribute, options[:message])
54
+ end
55
+
56
+ # Helper method to return the base expected error:
57
+ # ActiveModel::StrictValidationFailed, a custom error, or nil
58
+ #
59
+ # @options_error
60
+ # param: strict_error <true or subclass of Exception> - either the flag
61
+ # to raise an error or the actual error to raise
62
+ # return: custom error, ActiveModel::StrictValidationFailed, or nil
63
+ def options_error(strict_error)
64
+ return if strict_error.nil?
65
+
66
+ if strict_error == true
67
+ ActiveModel::StrictValidationFailed
68
+ elsif strict_error.ancestors.include?(Exception)
69
+ strict_error
70
+ end
71
+ end
72
+ end
73
+
74
+ module ClassMethods
75
+ # Validates that an attribute is a subset of an expected set:
76
+ #
77
+ # class Foo
78
+ # include ActiveModel::Validations
79
+ #
80
+ # attr_accessor :thing, :something
81
+ #
82
+ # validates_subset :thing, ['something', 'another thing', 'a third thing']
83
+ # validates_subset :something, [:foo, :bar]
84
+ # end
85
+ #
86
+ # @validates_subset
87
+ # param: attribute_name <Symbol> - name of attribute to validate
88
+ # param: set <Array> - superset of all desired members
89
+ # param: options <Hash> - other common options to validate methods calls
90
+ # i.e. message: 'my custom error message'
91
+ # return: nil
92
+ def validates_subset(attribute_name, superset, options = {})
93
+ args = ValidatesSubset::Arguments.new(attribute_name, superset, options)
94
+ validates_with SubsetValidator, _merge_attributes(args.to_validation_attributes)
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,63 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ class TestAllowBlank
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :foo
7
+
8
+ validates_subset :foo, [1, 2, 3], allow_blank: true
9
+ end
10
+
11
+ describe 'allow blank' do
12
+ subject { sub = TestAllowBlank.new; sub.foo = value; sub }
13
+
14
+ context 'value is not blank' do
15
+ context 'value is a valid subset' do
16
+ let(:value) { [1] }
17
+
18
+ specify do
19
+ expect(subject).to be_valid
20
+ end
21
+ end
22
+
23
+ context 'value is not a valid subset' do
24
+ let(:value) { %i[a b c] }
25
+
26
+ specify do
27
+ expect(subject).to_not be_valid
28
+ end
29
+ end
30
+
31
+ context 'value is not an array' do
32
+ let(:value) { 'bar' }
33
+
34
+ specify do
35
+ expect(subject).to_not be_valid
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'value is blank' do
41
+ let(:value) { nil }
42
+
43
+ specify do
44
+ expect(subject).to be_valid
45
+ end
46
+
47
+ context 'an empty array' do
48
+ let(:value) { [] }
49
+
50
+ specify do
51
+ expect(subject).to be_valid
52
+ end
53
+ end
54
+
55
+ context 'an empty hash' do
56
+ let(:value) { {} }
57
+
58
+ specify do
59
+ expect(subject).to be_valid
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,63 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ class TestAllowNil
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :foo
7
+
8
+ validates_subset :foo, [1, 2, 3], allow_nil: true
9
+ end
10
+
11
+ describe 'allow nil' do
12
+ subject { sub = TestAllowNil.new; sub.foo = value; sub }
13
+
14
+ context 'value is not nil' do
15
+ context 'value is a valid subset' do
16
+ let(:value) { [1] }
17
+
18
+ specify do
19
+ expect(subject).to be_valid
20
+ end
21
+ end
22
+
23
+ context 'value is not a valid subset' do
24
+ let(:value) { %i[a b c] }
25
+
26
+ specify do
27
+ expect(subject).to_not be_valid
28
+ end
29
+ end
30
+
31
+ context 'value is not an array' do
32
+ let(:value) { 'bar' }
33
+
34
+ specify do
35
+ expect(subject).to_not be_valid
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'value is nil' do
41
+ let(:value) { nil }
42
+
43
+ specify do
44
+ expect(subject).to be_valid
45
+ end
46
+ end
47
+
48
+ context 'an empty array' do
49
+ let(:value) { [] }
50
+
51
+ specify do
52
+ expect(subject).to be_valid
53
+ end
54
+ end
55
+
56
+ context 'an empty hash' do
57
+ let(:value) { {} }
58
+
59
+ specify do
60
+ expect(subject).to_not be_valid
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,58 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ describe 'if condition' do
4
+ subject { sub = TestIfTrue.new; sub.foo = value; sub }
5
+
6
+ context 'if condition results in true' do
7
+
8
+ class TestIfTrue
9
+ include ActiveModel::Validations
10
+
11
+ attr_accessor :foo
12
+
13
+ validates_subset :foo, [1, 2, 3], if: -> { true }
14
+ end
15
+
16
+ context 'value is a valid subset' do
17
+ let(:value) { [1] }
18
+
19
+ specify do
20
+ expect(subject).to be_valid
21
+ end
22
+ end
23
+
24
+ context 'value is not a valid subset' do
25
+ let(:value) { %i[a b c] }
26
+
27
+ specify do
28
+ expect(subject).to_not be_valid
29
+ end
30
+ end
31
+
32
+ context 'value is not an array' do
33
+ let(:value) { 'bar' }
34
+
35
+ specify do
36
+ expect(subject).to_not be_valid
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'condition results is not true' do
42
+ class TestIfFalse
43
+ include ActiveModel::Validations
44
+
45
+ attr_accessor :foo
46
+
47
+ validates_subset :foo, [1, 2, 3], if: -> { false }
48
+ end
49
+
50
+ subject { sub = TestIfFalse.new; sub.foo = value; sub }
51
+
52
+ let(:value) { anything }
53
+
54
+ specify do
55
+ expect(subject).to be_valid
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ class TestMessage
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :foo
7
+
8
+ validates_subset :foo, [1, 2, 3], message: 'Oh no spaghettios'
9
+ end
10
+
11
+ describe 'custom messaging' do
12
+ subject { sub = TestMessage.new; sub.foo = value; sub }
13
+
14
+ context 'value is a valid subset' do
15
+ let(:value) { [1] }
16
+
17
+ specify do
18
+ expect(subject).to be_valid
19
+ end
20
+ end
21
+
22
+ context 'value is not a valid subset' do
23
+ let(:value) { %i[a b c] }
24
+
25
+ specify do
26
+ expect(subject).to_not be_valid
27
+ end
28
+
29
+ it 'sets the custom error message' do
30
+ subject.validate
31
+ expect(subject.errors[:foo].first).to match(/Oh no spaghettios/)
32
+ end
33
+ end
34
+
35
+ context 'value is not an array' do
36
+ let(:value) { 'bar' }
37
+
38
+ specify do
39
+ expect(subject).to_not be_valid
40
+ end
41
+
42
+ it 'sets the custom error message' do
43
+ subject.validate
44
+ expect(subject.errors[:foo].first).to match(/Oh no spaghettios/)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,73 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ class TestMultiple
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :foo
7
+
8
+ validates_subset :foo, [1, 2, 3], allow_blank: true, message: 'oh no spaghettios'
9
+ end
10
+
11
+ describe 'multiple validations' do
12
+ subject { sub = TestMultiple.new; sub.foo = value; sub }
13
+
14
+ context 'value is not blank' do
15
+ context 'value is a valid subset' do
16
+ let(:value) { [1] }
17
+
18
+ specify do
19
+ expect(subject).to be_valid
20
+ end
21
+ end
22
+
23
+ context 'value is not a valid subset' do
24
+ let(:value) { %i[a b c] }
25
+
26
+ specify do
27
+ expect(subject).to_not be_valid
28
+ end
29
+
30
+ it 'has the specified error message' do
31
+ subject.validate
32
+ expect(subject.errors.full_messages.to_s).to match(/oh no spaghettios/)
33
+ end
34
+ end
35
+
36
+ context 'value is not an array' do
37
+ let(:value) { 'bar' }
38
+
39
+ specify do
40
+ expect(subject).to_not be_valid
41
+ end
42
+
43
+ it 'has the specified error message' do
44
+ subject.validate
45
+ expect(subject.errors.full_messages.to_s).to match(/oh no spaghettios/)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'value is blank' do
51
+ let(:value) { nil }
52
+
53
+ specify do
54
+ expect(subject).to be_valid
55
+ end
56
+
57
+ context 'an empty array' do
58
+ let(:value) { [] }
59
+
60
+ specify do
61
+ expect(subject).to be_valid
62
+ end
63
+ end
64
+
65
+ context 'an empty hash' do
66
+ let(:value) { {} }
67
+
68
+ specify do
69
+ expect(subject).to be_valid
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ describe 'on' do
4
+ class TestOn
5
+ include ActiveModel::Validations
6
+
7
+ attr_accessor :foo
8
+
9
+ validates_subset :foo, [1, 2, 3], on: :special_method
10
+
11
+ def special_method; end
12
+ end
13
+
14
+ subject { sub = TestOn.new; sub.foo = value; sub }
15
+
16
+ it 'adds the options to the validation' do
17
+ options = TestOn.validators_on(:foo).first.options
18
+ expect(options).to include(:on)
19
+ expect(options[:on]).to eq(:special_method)
20
+ end
21
+ end
@@ -0,0 +1,77 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ describe 'strict' do
4
+ context 'default exception for strict validation' do
5
+ class TestStrictDefault
6
+ include ActiveModel::Validations
7
+
8
+ attr_accessor :foo
9
+
10
+ validates_subset :foo, [1, 2, 3], strict: true
11
+ end
12
+
13
+ subject { sub = TestStrictDefault.new; sub.foo = value; sub }
14
+
15
+ context 'input is valid' do
16
+ let(:value) { [1] }
17
+
18
+ it 'is valid' do
19
+ expect(subject).to be_valid
20
+ end
21
+
22
+ it 'does not raise any errors' do
23
+ expect do
24
+ subject.valid?
25
+ end.to_not raise_error
26
+ end
27
+ end
28
+
29
+ context 'input is invalid' do
30
+ let(:value) { 'banana' }
31
+
32
+ it 'raises an error' do
33
+ expect do
34
+ subject.valid?
35
+ end.to raise_error
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'specific exception for strict validation' do
41
+ class TestStrictSpecific
42
+ class SomeStrictError < StandardError; end
43
+
44
+ include ActiveModel::Validations
45
+
46
+ attr_accessor :foo
47
+
48
+ validates_subset :foo, [1, 2, 3], strict: SomeStrictError
49
+ end
50
+
51
+ subject { sub = TestStrictSpecific.new; sub.foo = value; sub }
52
+
53
+ context 'input is valid' do
54
+ let(:value) { [1] }
55
+
56
+ it 'is valid' do
57
+ expect(subject).to be_valid
58
+ end
59
+
60
+ it 'does not raise any errors' do
61
+ expect do
62
+ subject.valid?
63
+ end.to_not raise_error
64
+ end
65
+ end
66
+
67
+ context 'input is invalid' do
68
+ let(:value) { 'banana' }
69
+
70
+ it 'raises the custom error' do
71
+ expect do
72
+ subject.valid?
73
+ end.to raise_error(TestStrictSpecific::SomeStrictError)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,76 @@
1
+ require_relative '../../../lib/validates_subset'
2
+
3
+ describe 'unless condition' do
4
+ subject { sub = TestUnlessTrue.new; sub.foo = value; sub }
5
+
6
+ context 'if condition results in true' do
7
+
8
+ class TestUnlessTrue
9
+ include ActiveModel::Validations
10
+
11
+ attr_accessor :foo
12
+
13
+ validates_subset :foo, [1, 2, 3], unless: -> { true }
14
+ end
15
+
16
+ context 'value is a valid subset' do
17
+ let(:value) { [1] }
18
+
19
+ specify do
20
+ expect(subject).to be_valid
21
+ end
22
+ end
23
+
24
+ context 'value is not a valid subset' do
25
+ let(:value) { %i[a b c] }
26
+
27
+ specify do
28
+ expect(subject).to be_valid
29
+ end
30
+ end
31
+
32
+ context 'value is not an array' do
33
+ let(:value) { 'bar' }
34
+
35
+ specify do
36
+ expect(subject).to be_valid
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'condition results is not true' do
42
+ class TestUnlessFalse
43
+ include ActiveModel::Validations
44
+
45
+ attr_accessor :foo
46
+
47
+ validates_subset :foo, [1, 2, 3], unless: -> { false }
48
+ end
49
+
50
+ subject { sub = TestUnlessFalse.new; sub.foo = value; sub }
51
+
52
+ context 'value is a valid subset' do
53
+ let(:value) { [1] }
54
+
55
+ specify do
56
+ expect(subject).to be_valid
57
+ end
58
+ end
59
+
60
+ context 'value is not a valid subset' do
61
+ let(:value) { %i[a b c] }
62
+
63
+ specify do
64
+ expect(subject).to_not be_valid
65
+ end
66
+ end
67
+
68
+ context 'value is not an array' do
69
+ let(:value) { 'bar' }
70
+
71
+ specify do
72
+ expect(subject).to_not be_valid
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,46 @@
1
+ require_relative '../../lib/validates_subset'
2
+
3
+ class ActiveModelTestClass
4
+ include ActiveModel::Validations
5
+
6
+ attr_accessor :foo
7
+
8
+ validates_subset :foo, [1, 2, 3]
9
+
10
+ def initialize(foo)
11
+ @foo = foo
12
+ end
13
+ end
14
+
15
+ describe ValidatesSubset do
16
+ subject { ActiveModelTestClass.new(attribute_value) }
17
+
18
+ context 'attribute is a valid subset' do
19
+ let(:attribute_value) { [1, 2] }
20
+
21
+ it 'is valid' do
22
+ expect(subject).to be_valid
23
+ end
24
+ end
25
+
26
+ context 'attribute is not a valid subset' do
27
+ context 'value is still an array' do
28
+ let(:attribute_value) { [5, 6] }
29
+
30
+ it 'does not error' do
31
+ expect do
32
+ subject.validate
33
+ end.to_not raise_error
34
+ end
35
+
36
+ it 'is not valid' do
37
+ expect(subject).to_not be_valid
38
+ end
39
+
40
+ it 'has an error message with the key of the validated attribute' do
41
+ subject.validate
42
+ expect(subject.errors).to have_key(:foo)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,84 @@
1
+ require_relative '../../lib/arguments'
2
+
3
+ module ValidatesSubset
4
+ describe Arguments do
5
+
6
+ let(:attribute_name) { :foo }
7
+ let(:superset) { %w(bar qux) }
8
+ let(:options) { { extra: :options } }
9
+
10
+ subject { described_class.new(attribute_name, superset, options) }
11
+
12
+ describe '#to_validation_attributes' do
13
+ context 'all pertinent attributes are present' do
14
+ specify do
15
+ expect(subject.to_validation_attributes).to eq([attribute_name, { subset: superset }.merge(options)])
16
+ end
17
+ end
18
+
19
+ context 'superset is nil' do
20
+ let(:superset) { nil }
21
+
22
+ specify do
23
+ expect(subject.to_validation_attributes).to eq([attribute_name, { subset: superset }.merge(options)])
24
+ end
25
+ end
26
+
27
+ context 'attribute_name is nil' do
28
+ let(:attribute_name) { nil }
29
+
30
+ specify do
31
+ expect(subject.to_validation_attributes).to eq([attribute_name, { subset: superset }.merge(options)])
32
+ end
33
+ end
34
+
35
+ context 'options is nil' do
36
+ let(:options) { nil }
37
+
38
+ specify do
39
+ expect(subject.to_validation_attributes).to eq([attribute_name, { subset: superset }])
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#subset' do
45
+ context 'subset is given' do
46
+ specify do
47
+ expect(subject.send(:subset)).to eq({ subset: superset })
48
+ end
49
+ end
50
+
51
+ context 'subset is nil' do
52
+ let(:superset) { nil }
53
+
54
+ specify do
55
+ expect(subject.send(:subset)).to eq({ subset: nil })
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#merged_options' do
61
+ context 'options are present' do
62
+ specify do
63
+ expect(subject.send(:merged_options)).to eq({ subset: superset }.merge(options))
64
+ end
65
+ end
66
+
67
+ context 'options is nil' do
68
+ let(:options) { nil }
69
+
70
+ specify do
71
+ expect(subject.send(:merged_options)).to eq({ subset: superset })
72
+ end
73
+ end
74
+
75
+ context 'options is a blank hash' do
76
+ let(:options) { {} }
77
+
78
+ specify do
79
+ expect(subject.send(:merged_options)).to eq({ subset: superset })
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,77 @@
1
+ require_relative '../../lib/validates_subset'
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ describe SubsetValidator do
6
+ describe '#initialize' do
7
+ let(:options) { { attributes: [:thing], foo: :bar, subset: [] } }
8
+ subject { described_class.new(options) }
9
+
10
+ it 'sets the options instance variable' do
11
+ expect(subject.instance_variable_get(:@options)).to_not be_nil
12
+ end
13
+
14
+ it 'removes the attributes from the options' do
15
+ expect(subject.instance_variable_get(:@options)).to_not have_key(:attributes)
16
+ end
17
+
18
+ it 'adds a message to the options' do
19
+ expect(subject.instance_variable_get(:@options)).to have_key(:message)
20
+ expect(subject.instance_variable_get(:@options)[:message]).to_not be_blank
21
+ end
22
+ end
23
+
24
+ describe '#validate_each' do
25
+ let(:return_value) { nil }
26
+ subject { described_class.new(attributes: [:foo]) }
27
+
28
+ before do
29
+ allow(subject).to receive(:is_subset?) { return_value }
30
+ allow(subject).to receive(:add_errors_or_raise) { nil }
31
+ end
32
+
33
+ it 'calls the validation logic' do
34
+ expect(subject).to receive(:is_subset?)
35
+ subject.validate_each(anything, anything, anything)
36
+ end
37
+
38
+ context 'validation passes' do
39
+ let(:return_value) { true }
40
+
41
+ it 'does not add errors on the validated model' do
42
+ expect(subject).to_not receive(:add_errors_or_raise)
43
+ subject.validate_each(anything, anything, anything)
44
+ end
45
+
46
+ it 'does not throw any errors' do
47
+ expect do
48
+ subject.validate_each(anything, anything, anything)
49
+ end.to_not raise_error
50
+ end
51
+ end
52
+
53
+ context 'validation fails' do
54
+ let(:return_value) { false }
55
+
56
+ it 'adds values to the errors on the validated model' do
57
+ expect(subject).to receive(:add_errors_or_raise)
58
+ subject.validate_each(anything, anything, anything)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '.validate_subset' do
65
+ class IncludedClass
66
+ include ActiveModel::Validations
67
+ end
68
+
69
+ it 'validates with SubsetValidator' do
70
+ included_class = IncludedClass
71
+ expect(included_class).to receive(:validates_with)
72
+ .with(SubsetValidator, anything)
73
+ included_class.validates_subset(anything, anything)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,26 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+ require_relative './version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'validates_subset'
7
+ s.version = ValidatesSubset::VERSION
8
+ s.summary = %q{Subset validation for ActiveModel/ActiveRecord models}
9
+ s.description = %q{This library helps validate that an attribute is a subset of a specified set}
10
+ s.authors = ['Jake Yesbeck']
11
+ s.email = 'yesbeckjs@gmail.com'
12
+ s.homepage = 'http://rubygems.org/gems/validates_subset'
13
+ s.license = 'MIT'
14
+
15
+ s.require_paths = ['lib']
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = s.files.grep(/^spec\//)
18
+
19
+ s.required_ruby_version = '>= 1.9.3'
20
+
21
+ s.add_dependency 'activemodel', '>= 3.0'
22
+
23
+ s.add_development_dependency 'bundler', '~> 1.3'
24
+ s.add_development_dependency 'rake', '~> 10.0'
25
+ s.add_development_dependency 'rspec', '~> 3.0'
26
+ end
data/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module ValidatesSubset
2
+ VERSION = '1.0.2'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: validates_subset
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jake Yesbeck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-19 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: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.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.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.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
+ description: This library helps validate that an attribute is a subset of a specified
70
+ set
71
+ email: yesbeckjs@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - Guardfile
81
+ - README.md
82
+ - lib/arguments.rb
83
+ - lib/validates_subset.rb
84
+ - spec/integration/modifiers/allow_blank_spec.rb
85
+ - spec/integration/modifiers/allow_nil_spec.rb
86
+ - spec/integration/modifiers/if_spec.rb
87
+ - spec/integration/modifiers/message_spec.rb
88
+ - spec/integration/modifiers/multiple_spec.rb
89
+ - spec/integration/modifiers/on_spec.rb
90
+ - spec/integration/modifiers/strict_spec.rb
91
+ - spec/integration/modifiers/unless_spec.rb
92
+ - spec/integration/validations_spec.rb
93
+ - spec/lib/arguments_spec.rb
94
+ - spec/lib/validates_subset_spec.rb
95
+ - validates_subset.gemspec
96
+ - version.rb
97
+ homepage: http://rubygems.org/gems/validates_subset
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 1.9.3
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.4.6
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Subset validation for ActiveModel/ActiveRecord models
121
+ test_files:
122
+ - spec/integration/modifiers/allow_blank_spec.rb
123
+ - spec/integration/modifiers/allow_nil_spec.rb
124
+ - spec/integration/modifiers/if_spec.rb
125
+ - spec/integration/modifiers/message_spec.rb
126
+ - spec/integration/modifiers/multiple_spec.rb
127
+ - spec/integration/modifiers/on_spec.rb
128
+ - spec/integration/modifiers/strict_spec.rb
129
+ - spec/integration/modifiers/unless_spec.rb
130
+ - spec/integration/validations_spec.rb
131
+ - spec/lib/arguments_spec.rb
132
+ - spec/lib/validates_subset_spec.rb