lotus-validations 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/LICENSE.md +1 -1
- data/README.md +46 -1
- data/lib/lotus/validations.rb +14 -2
- data/lib/lotus/validations/attribute.rb +31 -31
- data/lib/lotus/validations/attribute_definer.rb +47 -3
- data/lib/lotus/validations/error.rb +40 -6
- data/lib/lotus/validations/errors.rb +6 -3
- data/lib/lotus/validations/validation_set.rb +2 -1
- data/lib/lotus/validations/validator.rb +9 -8
- data/lib/lotus/validations/version.rb +1 -1
- data/lotus-validations.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddd486452556cb357a7742a46ce462e15ba9c206
|
4
|
+
data.tar.gz: 48fbf0ec796aaf18df2a2d138daed1c25f3335dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c25f6c2b8dd39d3d4cba3c0c7473c85f9572c23124d1e02cb5c354d145c4315262bbc576f6d99939048a1f58c6f68e4b6135542d33a843e8309f6f794c4ba771
|
7
|
+
data.tar.gz: 8bb02cb2c960d93b6e146cd61a4cd68fc5a0292188c8e61726a1e0bf7e09d5c3246e2d20de3adb2367756429e1bbbb4ad7750bfffd0544252cd6b4d94220e3b6
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# Lotus::Validations
|
2
2
|
Validations mixin for Ruby objects
|
3
3
|
|
4
|
+
## v0.2.4 - 2015-01-30
|
5
|
+
### Added
|
6
|
+
- [Steve Hodgkiss] Introduced `Lotus::Validations::Error#attribute_name`
|
7
|
+
- [Steve Hodgkiss] Nested validations
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- [Steve Hodgkiss] `Lotus::Validations::Error#name` returns the complete attribute name (Eg. `first_name` or `address.street`)
|
11
|
+
|
4
12
|
## v0.2.3 - 2015-01-12
|
5
13
|
### Added
|
6
14
|
- [Luca Guidi] Compatibility with Lotus::Entity
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -363,6 +363,51 @@ The other reason is that this isn't an effective way to ensure uniqueness of a v
|
|
363
363
|
|
364
364
|
Please read more at: [The Perils of Uniqueness Validations](http://robots.thoughtbot.com/the-perils-of-uniqueness-validations).
|
365
365
|
|
366
|
+
### Nested validations
|
367
|
+
|
368
|
+
Nested validations are handled with a nested block syntax.
|
369
|
+
|
370
|
+
```ruby
|
371
|
+
class ShippingDetails
|
372
|
+
include Lotus::Validations
|
373
|
+
|
374
|
+
attribute :full_name, presence: true
|
375
|
+
|
376
|
+
attribute :address do
|
377
|
+
attribute :street, presence: true
|
378
|
+
attribute :city, presence: true
|
379
|
+
attribute :country, presence: true
|
380
|
+
attribute :postal_code, presence: true, format: /.../
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
validator = ShippingDetails.new
|
385
|
+
validator.valid? # => false
|
386
|
+
```
|
387
|
+
|
388
|
+
Bulk operations on errors are guaranteed by `#each`.
|
389
|
+
This method yields a **flatten collection of errors**.
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
validator.errors.each do |error|
|
393
|
+
error.name
|
394
|
+
# => on the first iteration it returns "full_name"
|
395
|
+
# => the second time it returns "address.street" and so on..
|
396
|
+
end
|
397
|
+
```
|
398
|
+
|
399
|
+
Errors for a specific attribute can be accessed via `#for`.
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
error = validator.errors.for('full_name').first
|
403
|
+
error.name # => "full_name"
|
404
|
+
error.attribute_name # => "full_name"
|
405
|
+
|
406
|
+
error = validator.errors.for('address.street').first
|
407
|
+
error.name # => "address.street"
|
408
|
+
error.attribute_name # => "street"
|
409
|
+
```
|
410
|
+
|
366
411
|
### Composable validations
|
367
412
|
|
368
413
|
Validations can be reused via composition:
|
@@ -504,4 +549,4 @@ product.price # => 100
|
|
504
549
|
|
505
550
|
## Copyright
|
506
551
|
|
507
|
-
Copyright 2014-2015 Luca Guidi – Released under MIT License
|
552
|
+
Copyright © 2014-2015 Luca Guidi – Released under MIT License
|
data/lib/lotus/validations.rb
CHANGED
@@ -208,12 +208,24 @@ module Lotus
|
|
208
208
|
#
|
209
209
|
# @since 0.1.0
|
210
210
|
def valid?
|
211
|
-
|
212
|
-
@errors = validator.validate
|
211
|
+
validate
|
213
212
|
|
214
213
|
errors.empty?
|
215
214
|
end
|
216
215
|
|
216
|
+
# Validates the object.
|
217
|
+
#
|
218
|
+
# @return [Errors]
|
219
|
+
#
|
220
|
+
# @since 0.2.4
|
221
|
+
# @api private
|
222
|
+
#
|
223
|
+
# @see Lotus::Attribute#nested
|
224
|
+
def validate
|
225
|
+
validator = Validator.new(defined_validations, read_attributes, errors)
|
226
|
+
validator.validate
|
227
|
+
end
|
228
|
+
|
217
229
|
# Iterates thru the defined attributes and their values
|
218
230
|
#
|
219
231
|
# @param blk [Proc] a block
|
@@ -34,20 +34,30 @@ module Lotus
|
|
34
34
|
#
|
35
35
|
# @since 0.2.0
|
36
36
|
# @api private
|
37
|
-
def initialize(attributes, name, value, validations)
|
37
|
+
def initialize(attributes, name, value, validations, errors)
|
38
38
|
@attributes = attributes
|
39
39
|
@name = name
|
40
40
|
@value = value
|
41
41
|
@validations = validations
|
42
|
-
@errors =
|
42
|
+
@errors = errors
|
43
43
|
end
|
44
44
|
|
45
45
|
# @api private
|
46
46
|
# @since 0.2.0
|
47
47
|
def validate
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
presence
|
49
|
+
acceptance
|
50
|
+
|
51
|
+
return if skip?
|
52
|
+
|
53
|
+
format
|
54
|
+
inclusion
|
55
|
+
exclusion
|
56
|
+
size
|
57
|
+
confirmation
|
58
|
+
nested
|
59
|
+
|
60
|
+
@errors
|
51
61
|
end
|
52
62
|
|
53
63
|
# @api private
|
@@ -183,6 +193,21 @@ module Lotus
|
|
183
193
|
end
|
184
194
|
end
|
185
195
|
|
196
|
+
# Validates nested Lotus Validations objects
|
197
|
+
#
|
198
|
+
# @since 0.2.4
|
199
|
+
# @api private
|
200
|
+
def nested
|
201
|
+
_validate(__method__) do |validator|
|
202
|
+
errors = value.validate
|
203
|
+
errors.each do |error|
|
204
|
+
new_error = Error.new(error.attribute, error.validation, error.expected, error.actual, @name)
|
205
|
+
@errors.add new_error.attribute, new_error
|
206
|
+
end
|
207
|
+
true
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
186
211
|
# @since 0.1.0
|
187
212
|
# @api private
|
188
213
|
def skip?
|
@@ -199,31 +224,6 @@ module Lotus
|
|
199
224
|
BlankValueChecker.new(@value).blank_value?
|
200
225
|
end
|
201
226
|
|
202
|
-
# Run the defined validations
|
203
|
-
#
|
204
|
-
# @since 0.2.0
|
205
|
-
# @api private
|
206
|
-
def _run_validations
|
207
|
-
presence
|
208
|
-
acceptance
|
209
|
-
|
210
|
-
return if skip?
|
211
|
-
|
212
|
-
format
|
213
|
-
inclusion
|
214
|
-
exclusion
|
215
|
-
size
|
216
|
-
confirmation
|
217
|
-
end
|
218
|
-
|
219
|
-
# @api private
|
220
|
-
# @since 0.2.0
|
221
|
-
def _with_cleared_errors
|
222
|
-
@errors.clear
|
223
|
-
yield
|
224
|
-
@errors.dup.tap {|_| @errors.clear }
|
225
|
-
end
|
226
|
-
|
227
227
|
# Reads an attribute from the validator.
|
228
228
|
#
|
229
229
|
# @since 0.2.0
|
@@ -238,7 +238,7 @@ module Lotus
|
|
238
238
|
# @api private
|
239
239
|
def _validate(validation)
|
240
240
|
if (validator = @validations[validation]) && !(yield validator)
|
241
|
-
@errors.
|
241
|
+
@errors.add(@name, Error.new(@name, validation, @validations.fetch(validation), @value))
|
242
242
|
end
|
243
243
|
end
|
244
244
|
end
|
@@ -253,9 +253,14 @@ module Lotus
|
|
253
253
|
#
|
254
254
|
# signup = Signup.new(password: 'short')
|
255
255
|
# signup.valid? # => false
|
256
|
-
def attribute(name, options = {})
|
257
|
-
|
258
|
-
|
256
|
+
def attribute(name, options = {}, &block)
|
257
|
+
if block_given?
|
258
|
+
define_nested_attribute(name, options, &block)
|
259
|
+
validates(name, {})
|
260
|
+
else
|
261
|
+
define_attribute(name, options)
|
262
|
+
validates(name, options)
|
263
|
+
end
|
259
264
|
end
|
260
265
|
|
261
266
|
# Set of user defined attributes
|
@@ -285,6 +290,16 @@ module Lotus
|
|
285
290
|
end
|
286
291
|
end
|
287
292
|
|
293
|
+
# @since 0.2.4
|
294
|
+
# @api private
|
295
|
+
def define_nested_attribute(name, options, &block)
|
296
|
+
nested_class = build_validation_class(&block)
|
297
|
+
define_lazy_reader(name, nested_class)
|
298
|
+
define_coerced_writer(name, nested_class)
|
299
|
+
defined_attributes.add(name.to_s)
|
300
|
+
validates(name, nested: true)
|
301
|
+
end
|
302
|
+
|
288
303
|
# @since 0.2.2
|
289
304
|
# @api private
|
290
305
|
def define_accessor(name, type)
|
@@ -320,6 +335,35 @@ module Lotus
|
|
320
335
|
@attributes.get(name)
|
321
336
|
end
|
322
337
|
end
|
338
|
+
|
339
|
+
# Defines a reader that will return a new instance of
|
340
|
+
# the given type if one is not already present
|
341
|
+
#
|
342
|
+
# @since 0.2.4
|
343
|
+
# @api private
|
344
|
+
def define_lazy_reader(name, type)
|
345
|
+
define_method(name) do
|
346
|
+
value = @attributes.get(name)
|
347
|
+
return value if value
|
348
|
+
|
349
|
+
type.new({}).tap do |val|
|
350
|
+
@attributes.set(name, val)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
# Creates a validation class and configures it with the
|
356
|
+
# given block.
|
357
|
+
#
|
358
|
+
# @since 0.2.4
|
359
|
+
# @api private
|
360
|
+
def build_validation_class(&block)
|
361
|
+
kls = Class.new do
|
362
|
+
include Lotus::Validations
|
363
|
+
end
|
364
|
+
kls.class_eval(&block)
|
365
|
+
kls
|
366
|
+
end
|
323
367
|
end
|
324
368
|
|
325
369
|
# Support for `Lotus::Entity`
|
@@ -4,12 +4,24 @@ module Lotus
|
|
4
4
|
#
|
5
5
|
# @since 0.1.0
|
6
6
|
class Error
|
7
|
+
# @since 0.2.4
|
8
|
+
# @api private
|
9
|
+
NAMESPACE_SEPARATOR = '.'.freeze
|
10
|
+
|
7
11
|
# The name of the attribute
|
8
12
|
#
|
9
13
|
# @return [Symbol] the name of the attribute
|
10
14
|
#
|
11
|
-
# @since 0.
|
12
|
-
|
15
|
+
# @since 0.2.4
|
16
|
+
#
|
17
|
+
# @see Lotus::Validations::Error#attribute
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# error = Error.new(:name, :presence, true, nil, 'author')
|
21
|
+
#
|
22
|
+
# error.attribute # => "author.name"
|
23
|
+
# error.attribute_name # => "name"
|
24
|
+
attr_reader :attribute_name
|
13
25
|
|
14
26
|
# The name of the validation
|
15
27
|
#
|
@@ -32,18 +44,40 @@ module Lotus
|
|
32
44
|
# @since 0.1.0
|
33
45
|
attr_reader :actual
|
34
46
|
|
47
|
+
# Returns the namespaced attribute name
|
48
|
+
#
|
49
|
+
# In cases where the error was pulled up from nested validators,
|
50
|
+
# `attribute` will be a namespaced string containing
|
51
|
+
# parent attribute names separated by a period.
|
52
|
+
#
|
53
|
+
# @since 0.1.0
|
54
|
+
#
|
55
|
+
# @see Lotus::Validations::Error#attribute_name
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# error = Error.new(:name, :presence, true, nil, 'author')
|
59
|
+
#
|
60
|
+
# error.attribute # => "author.name"
|
61
|
+
# error.attribute_name # => "name"
|
62
|
+
attr_accessor :attribute
|
63
|
+
|
35
64
|
# Initialize a validation error
|
36
65
|
#
|
37
|
-
# @param
|
66
|
+
# @param attribute_name [Symbol] the name of the attribute
|
38
67
|
# @param validation [Symbol] the name of the validation
|
39
68
|
# @param expected [Object] the expected value
|
40
69
|
# @param actual [Object] the actual value
|
70
|
+
# @param namespace [String] the optional namespace
|
41
71
|
#
|
42
72
|
# @since 0.1.0
|
43
73
|
# @api private
|
44
|
-
def initialize(
|
45
|
-
@
|
46
|
-
|
74
|
+
def initialize(attribute_name, validation, expected, actual, namespace = nil)
|
75
|
+
@attribute_name = attribute_name.to_s
|
76
|
+
@validation = validation
|
77
|
+
@expected = expected
|
78
|
+
@actual = actual
|
79
|
+
@namespace = namespace
|
80
|
+
@attribute = [@namespace, attribute_name].compact.join(NAMESPACE_SEPARATOR)
|
47
81
|
end
|
48
82
|
|
49
83
|
# Check if self equals to `other`
|
@@ -15,7 +15,7 @@ module Lotus
|
|
15
15
|
# @since 0.1.0
|
16
16
|
# @api private
|
17
17
|
def initialize
|
18
|
-
@errors = Hash.new
|
18
|
+
@errors = Hash.new
|
19
19
|
end
|
20
20
|
|
21
21
|
# Check if the set is empty
|
@@ -94,7 +94,10 @@ module Lotus
|
|
94
94
|
#
|
95
95
|
# @see Lotus::Validations::Error
|
96
96
|
def add(attribute, *errors)
|
97
|
-
|
97
|
+
if errors.any?
|
98
|
+
@errors[attribute] ||= []
|
99
|
+
@errors[attribute].push(*errors)
|
100
|
+
end
|
98
101
|
end
|
99
102
|
|
100
103
|
# Return the errors for the given attribute
|
@@ -103,7 +106,7 @@ module Lotus
|
|
103
106
|
#
|
104
107
|
# @since 0.1.0
|
105
108
|
def for(attribute)
|
106
|
-
@errors[
|
109
|
+
@errors.fetch(attribute) { [] }
|
107
110
|
end
|
108
111
|
|
109
112
|
# Check if the current set of errors equals to the one who belongs to
|
@@ -5,23 +5,24 @@ module Lotus
|
|
5
5
|
# @since 0.2.2
|
6
6
|
# @api private
|
7
7
|
class Validator
|
8
|
-
def initialize(validation_set, attributes)
|
8
|
+
def initialize(validation_set, attributes, errors)
|
9
9
|
@validation_set = validation_set
|
10
10
|
@attributes = attributes
|
11
|
+
@errors = errors
|
11
12
|
end
|
12
13
|
|
13
14
|
# @since 0.2.2
|
14
15
|
# @api private
|
15
16
|
def validate
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
@errors.clear
|
18
|
+
@validation_set.each do |name, validations|
|
19
|
+
value = @attributes[name]
|
20
|
+
value = @attributes[name.to_s] if value.nil?
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
22
|
+
attribute = Attribute.new(@attributes, name, value, validations, @errors)
|
23
|
+
attribute.validate
|
24
24
|
end
|
25
|
+
@errors
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
data/lotus-validations.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
spec.required_ruby_version = '>= 2.0.0'
|
21
21
|
|
22
|
-
spec.add_dependency 'lotus-utils', '~> 0.3', '>= 0.3.
|
22
|
+
spec.add_dependency 'lotus-utils', '~> 0.3', '>= 0.3.4'
|
23
23
|
|
24
24
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
25
25
|
spec.add_development_dependency 'minitest', '~> 5'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lotus-validations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-01-
|
12
|
+
date: 2015-01-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: lotus-utils
|
@@ -20,7 +20,7 @@ dependencies:
|
|
20
20
|
version: '0.3'
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.3.
|
23
|
+
version: 0.3.4
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
26
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -30,7 +30,7 @@ dependencies:
|
|
30
30
|
version: '0.3'
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.3.
|
33
|
+
version: 0.3.4
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: bundler
|
36
36
|
requirement: !ruby/object:Gem::Requirement
|