tram-validators 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ # Checks that a model instance can be found by id
2
+ #
3
+ # @example Checks that AdminPolicy.new(user).valid?
4
+ # validates :user_key, reference: { model: User, find_by: :key }
5
+ #
6
+ class ReferenceValidator < ActiveModel::EachValidator
7
+ def validate_each(record, attribute, value)
8
+ model = options.fetch(:model) { raise "You should define :model option" }
9
+ key = options.fetch(:find_by, :id)
10
+
11
+ return if model.find_by(key => value)
12
+ record.errors.add attribute, :not_found
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ # Compares size of array to given value or another attribute
2
+ #
3
+ # @example
4
+ # validates :names, size: { less_than: 6 }
5
+ # validates :values, size: { equal_to: :names }
6
+ #
7
+ class SizeValidator < ActiveModel::EachValidator
8
+ def validate_each(record, attribute, value)
9
+ size = value.size if value.is_a? Array
10
+ Tram::Validators::CONDITIONS.each do |key, block|
11
+ check(key, record, attribute, size, &block)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def check(condition, record, attribute, size)
18
+ return unless options.key? condition
19
+
20
+ limit = extract_limit(record, condition)
21
+ return if size && limit && yield(size, limit.to_i)
22
+
23
+ record.errors.add attribute, error_key(condition), size: size, limit: limit
24
+ end
25
+
26
+ def extract_limit(record, condition)
27
+ value = options[condition]
28
+ return value.to_s.to_i if value.to_s.to_i == value
29
+ value.to_s.split(".").inject(record) { |obj, meth| obj&.send(meth) }
30
+ end
31
+
32
+ def error_key(condition)
33
+ value = options[condition]
34
+ name = value.to_s.underscore.gsub(".", "_") unless value.to_s.to_i == value
35
+ ["size", condition, name].compact.join("_").to_sym
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ # Checks that value of some attribute is valid per se
2
+ # Otherwise populates the record with the attribute's validation errors
3
+ #
4
+ # With option `original_keys: true` it copies value's errors to the record
5
+ # under their original keys, as if the [#record] had attributes of the [#value].
6
+ # Otherwise the errors are copied under the [#attribute] key.
7
+ #
8
+ # This is necessary for validation of the form models, as well as
9
+ # other decorated objects.
10
+ #
11
+ # @example Checks that user.valid? returns true
12
+ # validates :user, validity: { nested_keys: true }
13
+ #
14
+ class ValidityValidator < ActiveModel::EachValidator
15
+ def validate_each(record, attribute, value)
16
+ if !value.respond_to? :invalid?
17
+ record.errors.add attribute, :invalidable, record: record,
18
+ attribute: attribute,
19
+ value: value
20
+ elsif value.invalid?
21
+ value.errors.messages.each do |key, messages|
22
+ error_key = Tram::Validators.error_key(key, attribute, options)
23
+ messages.each { |message| record.errors.add error_key, message }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require "pry"
2
+ require "tram-validators"
3
+
4
+ require_relative "support/invalid_with_error"
5
+
6
+ RSpec.configure do |config|
7
+ config.order = :random
8
+ config.filter_run focus: true
9
+ config.run_all_when_everything_filtered = true
10
+
11
+ # Prepare the Test namespace for constants defined in specs
12
+ config.around(:each) do |example|
13
+ Test = Class.new(Module)
14
+ example.run
15
+ Object.send :remove_const, :Test
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ RSpec::Matchers.define :be_invalid_with_error do |key, name = nil|
2
+ match do |object|
3
+ expect(object).not_to be_valid
4
+
5
+ error = object.errors.messages[key.to_sym].to_a.first
6
+ satisfy_condition = name ? match(/\.#{name}\z/) : be_present
7
+
8
+ expect(error).to satisfy_condition
9
+ end
10
+ end
@@ -0,0 +1,131 @@
1
+ RSpec.describe ConsistencyValidator do
2
+ before do
3
+ Test::Subject = Struct.new(:foo, :bar) do
4
+ include ActiveModel::Validations
5
+ end
6
+
7
+ Test::Subject.validates :foo, consistency: options
8
+ end
9
+
10
+ subject { Test::Subject.new(foo, double(baz: 2)) }
11
+
12
+ context "equal_to attribute" do
13
+ let(:options) { { equal_to: "bar.baz" } }
14
+
15
+ context "when attribute equals to limit" do
16
+ let(:foo) { 2 }
17
+ it { is_expected.to be_valid }
18
+ end
19
+
20
+ context "when attribute is less than limit" do
21
+ let(:foo) { 1 }
22
+ it { is_expected.to be_invalid_with_error :foo, :equal_to_bar_baz }
23
+ end
24
+
25
+ context "when attribute is greater than limit" do
26
+ let(:foo) { 3 }
27
+ it { is_expected.to be_invalid_with_error :foo, :equal_to_bar_baz }
28
+ end
29
+ end
30
+
31
+ context "other_than attribute" do
32
+ let(:options) { { other_than: "bar.baz" } }
33
+
34
+ context "when attribute equals to limit" do
35
+ let(:foo) { 2 }
36
+ it { is_expected.to be_invalid_with_error :foo, :other_than_bar_baz }
37
+ end
38
+
39
+ context "when attribute is less than limit" do
40
+ let(:foo) { 1 }
41
+ it { is_expected.to be_valid }
42
+ end
43
+
44
+ context "when attribute is greater than limit" do
45
+ let(:foo) { 3 }
46
+ it { is_expected.to be_valid }
47
+ end
48
+ end
49
+
50
+ context "greater_than attribute" do
51
+ let(:options) { { greater_than: "bar.baz" } }
52
+
53
+ context "when attribute equals to limit" do
54
+ let(:foo) { 2 }
55
+ it { is_expected.to be_invalid_with_error :foo, :greater_than_bar_baz }
56
+ end
57
+
58
+ context "when attribute is less than limit" do
59
+ let(:foo) { 1 }
60
+ it { is_expected.to be_invalid_with_error :foo, :greater_than_bar_baz }
61
+ end
62
+
63
+ context "when attribute is greater than limit" do
64
+ let(:foo) { 3 }
65
+ it { is_expected.to be_valid }
66
+ end
67
+ end
68
+
69
+ context "greater_than_or_equal_to attribute" do
70
+ let(:options) { { greater_than_or_equal_to: "bar.baz" } }
71
+
72
+ context "when attribute equals to limit" do
73
+ let(:foo) { 2 }
74
+ it { is_expected.to be_valid }
75
+ end
76
+
77
+ context "when attribute is less than limit" do
78
+ let(:foo) { 1 }
79
+ it do
80
+ is_expected.to be_invalid_with_error \
81
+ :foo, :greater_than_or_equal_to_bar_baz
82
+ end
83
+ end
84
+
85
+ context "when attribute is greater than limit" do
86
+ let(:foo) { 3 }
87
+ it { is_expected.to be_valid }
88
+ end
89
+ end
90
+
91
+ context "less_than attribute" do
92
+ let(:options) { { less_than: "bar.baz" } }
93
+
94
+ context "when attribute equals to limit" do
95
+ let(:foo) { 2 }
96
+ it { is_expected.to be_invalid_with_error :foo, :less_than_bar_baz }
97
+ end
98
+
99
+ context "when attribute is less than limit" do
100
+ let(:foo) { 1 }
101
+ it { is_expected.to be_valid }
102
+ end
103
+
104
+ context "when attribute is greater than limit" do
105
+ let(:foo) { 3 }
106
+ it { is_expected.to be_invalid_with_error :foo, :less_than_bar_baz }
107
+ end
108
+ end
109
+
110
+ context "less_than_or_equal_to attribute" do
111
+ let(:options) { { less_than_or_equal_to: "bar.baz" } }
112
+
113
+ context "when attribute equals to limit" do
114
+ let(:foo) { 2 }
115
+ it { is_expected.to be_valid }
116
+ end
117
+
118
+ context "when attribute is less than limit" do
119
+ let(:foo) { 1 }
120
+ it { is_expected.to be_valid }
121
+ end
122
+
123
+ context "when attribute is greater than limit" do
124
+ let(:foo) { 3 }
125
+ it do
126
+ is_expected.to be_invalid_with_error \
127
+ :foo, :less_than_or_equal_to_bar_baz
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,58 @@
1
+ RSpec.describe ContractValidator do
2
+ before do
3
+ class Test::Contract < SimpleDelegator
4
+ include ActiveModel::Validations
5
+ validates :bar, presence: true
6
+ end
7
+
8
+ Test::Subject = Struct.new(:foo) do
9
+ include ActiveModel::Validations
10
+ end
11
+
12
+ Test::Subject.validates :foo, contract: { policy: Test::Contract, **opts }
13
+ end
14
+
15
+ subject { Test::Subject.new(double(bar: value)) }
16
+
17
+ context "without key option" do
18
+ let(:opts) { {} }
19
+
20
+ context "when value satisfies contract" do
21
+ let(:value) { 1 }
22
+ it { is_expected.to be_valid }
23
+ end
24
+
25
+ context "when value breaks contract" do
26
+ let(:value) { nil }
27
+ it { is_expected.to be_invalid_with_error :foo }
28
+ end
29
+ end
30
+
31
+ context "with original_keys option" do
32
+ let(:opts) { { original_keys: true } }
33
+
34
+ context "when value satisfies contract" do
35
+ let(:value) { 1 }
36
+ it { is_expected.to be_valid }
37
+ end
38
+
39
+ context "when value breaks contract" do
40
+ let(:value) { nil }
41
+ it { is_expected.to be_invalid_with_error :bar }
42
+ end
43
+ end
44
+
45
+ context "with nested_keys option" do
46
+ let(:opts) { { nested_keys: true } }
47
+
48
+ context "when value satisfies contract" do
49
+ let(:value) { 1 }
50
+ it { is_expected.to be_valid }
51
+ end
52
+
53
+ context "when value breaks contract" do
54
+ let(:value) { nil }
55
+ it { is_expected.to be_invalid_with_error "foo[bar]" }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,35 @@
1
+ RSpec.describe EachValidator do
2
+ before do
3
+ Test::Subject = Struct.new(:list) do
4
+ include ActiveModel::Validations
5
+ validates :list, each: { presence: true }
6
+ end
7
+ end
8
+
9
+ subject { Test::Subject.new(list) }
10
+
11
+ context "when all elements of array satisfy validation rule" do
12
+ let(:list) { %i(foo bar baz) }
13
+ it { is_expected.to be_valid }
14
+ end
15
+
16
+ context "when some element of array breaks validation rule" do
17
+ let(:list) { [:foo, :bar, nil] }
18
+ it { is_expected.to be_invalid_with_error "list[2]" }
19
+ end
20
+
21
+ context "when value is an empty array" do
22
+ let(:list) { [] }
23
+ it { is_expected.to be_valid }
24
+ end
25
+
26
+ context "when value is nil" do
27
+ let(:list) { :foo }
28
+ it { is_expected.to be_valid }
29
+ end
30
+
31
+ context "when value is neiter nil nor an array" do
32
+ let(:list) { :foo }
33
+ it { is_expected.to be_valid }
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ RSpec.describe OutcomeValidator do
2
+ before do
3
+ Test::Subject = Struct.new(:foo, :bar) do
4
+ include ActiveModel::Validations
5
+ validates :foo, outcome: { value: "bar.baz", presence: true }
6
+ end
7
+ end
8
+
9
+ subject { Test::Subject.new(1, bar) }
10
+
11
+ context "when outcome is valid" do
12
+ let(:bar) { double baz: 1 }
13
+ it { is_expected.to be_valid }
14
+ end
15
+
16
+ context "when outcome is invalid" do
17
+ let(:bar) { double baz: nil }
18
+ it { is_expected.to be_invalid_with_error :foo, :"bar_baz_presence" }
19
+ end
20
+
21
+ context "when outcome produces nil inside chain" do
22
+ let(:bar) { nil }
23
+ it "uses nil outcome" do
24
+ expect(subject).to be_invalid_with_error :foo, :"bar_baz_presence"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,347 @@
1
+ RSpec.describe SizeValidator do
2
+ before do
3
+ Test::Subject = Struct.new(:foo, :bar, :baz) do
4
+ include ActiveModel::Validations
5
+ end
6
+ end
7
+
8
+ let(:foo) { %w(Color Size) }
9
+ let(:bar) { double baz: 2 }
10
+ let(:baz) { double qux: %w(Green XXXL) }
11
+
12
+ subject { Test::Subject.new(foo, bar, baz) }
13
+
14
+ context "equal_to number" do
15
+ before { Test::Subject.validates :foo, size: { equal_to: 2 } }
16
+
17
+ context "when attribute has size equal to limit" do
18
+ let(:foo) { %w(Color Size) }
19
+ it { is_expected.to be_valid }
20
+ end
21
+
22
+ context "when attribute has size less than limit" do
23
+ let(:foo) { %w(Color) }
24
+ it { is_expected.to be_invalid_with_error :foo, :size_equal_to }
25
+ end
26
+
27
+ context "when attribute has size greater than limit" do
28
+ let(:foo) { %w(Color Size Style) }
29
+ it { is_expected.to be_invalid_with_error :foo, :size_equal_to }
30
+ end
31
+
32
+ context "when attribute is not an array" do
33
+ let(:foo) { 1 }
34
+ it { is_expected.to be_invalid_with_error :foo, :size_equal_to }
35
+ end
36
+ end
37
+
38
+ context "other_than number" do
39
+ before { Test::Subject.validates :foo, size: { other_than: 2 } }
40
+
41
+ context "when attribute has size equal to limit" do
42
+ let(:foo) { %w(Color Size) }
43
+ it { is_expected.to be_invalid_with_error :foo, :size_other_than }
44
+ end
45
+
46
+ context "when attribute has size less than limit" do
47
+ let(:foo) { %w(Color) }
48
+ it { is_expected.to be_valid }
49
+ end
50
+
51
+ context "when attribute has size greater than limit" do
52
+ let(:foo) { %w(Color Size Style) }
53
+ it { is_expected.to be_valid }
54
+ end
55
+
56
+ context "when attribute is not an array" do
57
+ let(:foo) { 1 }
58
+ it { is_expected.to be_invalid_with_error :foo, :size_other_than }
59
+ end
60
+ end
61
+
62
+ context "greater_than number" do
63
+ before { Test::Subject.validates :foo, size: { greater_than: 2 } }
64
+
65
+ context "when attribute has size equal to limit" do
66
+ let(:foo) { %w(Color Size) }
67
+ it { is_expected.to be_invalid_with_error :foo, :size_greater_than }
68
+ end
69
+
70
+ context "when attribute has size less than limit" do
71
+ let(:foo) { %w(Color) }
72
+ it { is_expected.to be_invalid_with_error :foo, :size_greater_than }
73
+ end
74
+
75
+ context "when attribute has size greater than limit" do
76
+ let(:foo) { %w(Color Size Style) }
77
+ it { is_expected.to be_valid }
78
+ end
79
+
80
+ context "when attribute is not an array" do
81
+ let(:foo) { 1 }
82
+ it { is_expected.to be_invalid_with_error :foo, :size_greater_than }
83
+ end
84
+ end
85
+
86
+ context "greater_than_or_equal_to number" do
87
+ before do
88
+ Test::Subject.validates :foo, size: { greater_than_or_equal_to: 2 }
89
+ end
90
+
91
+ context "when attribute has size equal to limit" do
92
+ let(:foo) { %w(Color Size) }
93
+ it { is_expected.to be_valid }
94
+ end
95
+
96
+ context "when attribute has size less than limit" do
97
+ let(:foo) { %w(Color) }
98
+ it do
99
+ is_expected
100
+ .to be_invalid_with_error :foo, :size_greater_than_or_equal_to
101
+ end
102
+ end
103
+
104
+ context "when attribute has size greater than limit" do
105
+ let(:foo) { %w(Color Size Style) }
106
+ it { is_expected.to be_valid }
107
+ end
108
+
109
+ context "when attribute is not an array" do
110
+ let(:foo) { 1 }
111
+ it do
112
+ is_expected
113
+ .to be_invalid_with_error :foo, :size_greater_than_or_equal_to
114
+ end
115
+ end
116
+ end
117
+
118
+ context "less_than number" do
119
+ before { Test::Subject.validates :foo, size: { less_than: 2 } }
120
+
121
+ context "when attribute has size equal to limit" do
122
+ let(:foo) { %w(Color Size) }
123
+ it { is_expected.to be_invalid_with_error :foo, :size_less_than }
124
+ end
125
+
126
+ context "when attribute has size less than limit" do
127
+ let(:foo) { %w(Color) }
128
+ it { is_expected.to be_valid }
129
+ end
130
+
131
+ context "when attribute has size greater than limit" do
132
+ let(:foo) { %w(Color Size Style) }
133
+ it { is_expected.to be_invalid_with_error :foo, :size_less_than }
134
+ end
135
+
136
+ context "when attribute is not an array" do
137
+ let(:foo) { 1 }
138
+ it { is_expected.to be_invalid_with_error :foo, :size_less_than }
139
+ end
140
+ end
141
+
142
+ context "less_than_or_equal_to number" do
143
+ before { Test::Subject.validates :foo, size: { less_than_or_equal_to: 2 } }
144
+
145
+ context "when attribute has size equal to limit" do
146
+ let(:foo) { %w(Color Size) }
147
+ it { is_expected.to be_valid }
148
+ end
149
+
150
+ context "when attribute has size less than limit" do
151
+ let(:foo) { %w(Color) }
152
+ it { is_expected.to be_valid }
153
+ end
154
+
155
+ context "when attribute has size greater than limit" do
156
+ let(:foo) { %w(Color Size Style) }
157
+ it do
158
+ is_expected.to be_invalid_with_error :foo, :size_less_than_or_equal_to
159
+ end
160
+ end
161
+
162
+ context "when attribute is not an array" do
163
+ let(:foo) { 1 }
164
+ it do
165
+ is_expected.to be_invalid_with_error :foo, :size_less_than_or_equal_to
166
+ end
167
+ end
168
+ end
169
+
170
+ context "equal_to attribute" do
171
+ before { Test::Subject.validates :foo, size: { equal_to: "bar.baz" } }
172
+
173
+ context "when attribute has size equal to limit" do
174
+ let(:foo) { %w(Color Size) }
175
+ it { is_expected.to be_valid }
176
+ end
177
+
178
+ context "when attribute has size less than limit" do
179
+ let(:foo) { %w(Color) }
180
+ it do
181
+ is_expected.to be_invalid_with_error :foo, :size_equal_to_bar_baz
182
+ end
183
+ end
184
+
185
+ context "when attribute has size greater than limit" do
186
+ let(:foo) { %w(Color Size Style) }
187
+ it do
188
+ is_expected.to be_invalid_with_error :foo, :size_equal_to_bar_baz
189
+ end
190
+ end
191
+
192
+ context "when attribute is not an array" do
193
+ let(:foo) { 1 }
194
+ it do
195
+ is_expected.to be_invalid_with_error :foo, :size_equal_to_bar_baz
196
+ end
197
+ end
198
+ end
199
+
200
+ context "other_than attribute" do
201
+ before { Test::Subject.validates :foo, size: { other_than: "bar.baz" } }
202
+
203
+ context "when attribute has size equal to limit" do
204
+ let(:foo) { %w(Color Size) }
205
+ it do
206
+ is_expected.to be_invalid_with_error :foo, :size_other_than_bar_baz
207
+ end
208
+ end
209
+
210
+ context "when attribute has size less than limit" do
211
+ let(:foo) { %w(Color) }
212
+ it { is_expected.to be_valid }
213
+ end
214
+
215
+ context "when attribute has size greater than limit" do
216
+ let(:foo) { %w(Color Size Style) }
217
+ it { is_expected.to be_valid }
218
+ end
219
+
220
+ context "when attribute is not an array" do
221
+ let(:foo) { 1 }
222
+ it do
223
+ is_expected.to be_invalid_with_error :foo, :size_other_than_bar_baz
224
+ end
225
+ end
226
+ end
227
+
228
+ context "greater_than attribute" do
229
+ before { Test::Subject.validates :foo, size: { greater_than: "bar.baz" } }
230
+
231
+ context "when attribute has size equal to limit" do
232
+ let(:foo) { %w(Color Size) }
233
+ it do
234
+ is_expected.to be_invalid_with_error :foo, :size_greater_than_bar_baz
235
+ end
236
+ end
237
+
238
+ context "when attribute has size less than limit" do
239
+ let(:foo) { %w(Color) }
240
+ it do
241
+ is_expected.to be_invalid_with_error :foo, :size_greater_than_bar_baz
242
+ end
243
+ end
244
+
245
+ context "when attribute has size greater than limit" do
246
+ let(:foo) { %w(Color Size Style) }
247
+ it { is_expected.to be_valid }
248
+ end
249
+
250
+ context "when attribute is not an array" do
251
+ let(:foo) { 1 }
252
+ it do
253
+ is_expected.to be_invalid_with_error :foo, :size_greater_than_bar_baz
254
+ end
255
+ end
256
+ end
257
+
258
+ context "greater_than_or_equal_to attribute" do
259
+ before do
260
+ Test::Subject.validates :foo,
261
+ size: { greater_than_or_equal_to: "bar.baz" }
262
+ end
263
+
264
+ context "when attribute has size equal to limit" do
265
+ let(:foo) { %w(Color Size) }
266
+ it { is_expected.to be_valid }
267
+ end
268
+
269
+ context "when attribute has size less than limit" do
270
+ let(:foo) { %w(Color) }
271
+ it do
272
+ is_expected
273
+ .to be_invalid_with_error :foo, :size_greater_than_or_equal_to_bar_baz
274
+ end
275
+ end
276
+
277
+ context "when attribute has size greater than limit" do
278
+ let(:foo) { %w(Color Size Style) }
279
+
280
+ it { is_expected.to be_valid }
281
+ end
282
+
283
+ context "when attribute is not an array" do
284
+ let(:foo) { 1 }
285
+ it do
286
+ is_expected
287
+ .to be_invalid_with_error :foo, :size_greater_than_or_equal_to_bar_baz
288
+ end
289
+ end
290
+ end
291
+
292
+ context "less_than attribute" do
293
+ before { Test::Subject.validates :foo, size: { less_than: "bar.baz" } }
294
+
295
+ context "when attribute has size equal to limit" do
296
+ let(:foo) { %w(Color Size) }
297
+ it { is_expected.to be_invalid_with_error :foo, :size_less_than_bar_baz }
298
+ end
299
+
300
+ context "when attribute has size less than limit" do
301
+ let(:foo) { %w(Color) }
302
+ it { is_expected.to be_valid }
303
+ end
304
+
305
+ context "when attribute has size greater than limit" do
306
+ let(:foo) { %w(Color Size Style) }
307
+ it { is_expected.to be_invalid_with_error :foo, :size_less_than_bar_baz }
308
+ end
309
+
310
+ context "when attribute is not an array" do
311
+ let(:foo) { 1 }
312
+ it { is_expected.to be_invalid_with_error :foo, :size_less_than_bar_baz }
313
+ end
314
+ end
315
+
316
+ context "less_than_or_equal_to attribute" do
317
+ before do
318
+ Test::Subject.validates :foo, size: { less_than_or_equal_to: "bar.baz" }
319
+ end
320
+
321
+ context "when attribute has size equal to limit" do
322
+ let(:foo) { %w(Color Size) }
323
+ it { is_expected.to be_valid }
324
+ end
325
+
326
+ context "when attribute has size less than limit" do
327
+ let(:foo) { %w(Color) }
328
+ it { is_expected.to be_valid }
329
+ end
330
+
331
+ context "when attribute has size greater than limit" do
332
+ let(:foo) { %w(Color Size Style) }
333
+ it do
334
+ is_expected.to be_invalid_with_error \
335
+ :foo, :size_less_than_or_equal_to_bar_baz
336
+ end
337
+ end
338
+
339
+ context "when attribute is not an array" do
340
+ let(:foo) { 1 }
341
+ it do
342
+ is_expected.to be_invalid_with_error \
343
+ :foo, :size_less_than_or_equal_to_bar_baz
344
+ end
345
+ end
346
+ end
347
+ end