lotus-validations 0.2.3 → 0.2.4

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 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