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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5c28b2bd092a3123b3449bd57ce7aa6bf641aef7
4
- data.tar.gz: 6def7642d5acb0049d3f9656ccb22617a7a0a97c
3
+ metadata.gz: ddd486452556cb357a7742a46ce462e15ba9c206
4
+ data.tar.gz: 48fbf0ec796aaf18df2a2d138daed1c25f3335dd
5
5
  SHA512:
6
- metadata.gz: 721ec4289267c55b2ac39e132568568023c3a51ba3ecf1faca3627d7a1a60a351985aa0dfb778808fbfa19f3dec305fd1830e179a1f5f8d1ae4252676130a81b
7
- data.tar.gz: 695f77817307e158789652f4a584bad04af0cf05e5f65d48068389a6db9e1153b613f827e45c25a5a3051c355eee28cc32889488988435798625a3b1f6d1a780
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Luca Guidi
1
+ Copyright © 2014-2015 Luca Guidi
2
2
 
3
3
  MIT License
4
4
 
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
@@ -208,12 +208,24 @@ module Lotus
208
208
  #
209
209
  # @since 0.1.0
210
210
  def valid?
211
- validator = Validator.new(defined_validations, read_attributes)
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
- _with_cleared_errors do
49
- _run_validations
50
- end
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.push(Error.new(@name, validation, @validations.fetch(validation), @value))
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
- define_attribute(name, options)
258
- validates(name, options)
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.1.0
12
- attr_reader :attribute
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 attribute [Symbol] the name of the attribute
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(attribute, validation, expected, actual)
45
- @attribute, @validation, @expected, @actual =
46
- attribute, validation, expected, actual
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 {|h,k| h[k] = [] }
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
- @errors[attribute].push(*errors) if errors.any?
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[attribute]
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
@@ -17,7 +17,8 @@ module Lotus
17
17
  :exclusion,
18
18
  :confirmation,
19
19
  :size,
20
- :type
20
+ :type,
21
+ :nested
21
22
  ].freeze
22
23
 
23
24
  # @since 0.2.2
@@ -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
- Errors.new.tap do |errors|
17
- @validation_set.each do |name, validations|
18
- value = @attributes[name]
19
- value = @attributes[name.to_s] if value.nil?
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
- attribute = Attribute.new(@attributes, name, value, validations)
22
- errors.add name, *attribute.validate
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
@@ -1,6 +1,6 @@
1
1
  module Lotus
2
2
  module Validations
3
3
  # @since 0.1.0
4
- VERSION = '0.2.3'.freeze
4
+ VERSION = '0.2.4'.freeze
5
5
  end
6
6
  end
@@ -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.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.3
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 00:00:00.000000000 Z
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.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.3
33
+ version: 0.3.4
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: bundler
36
36
  requirement: !ruby/object:Gem::Requirement