composable_validations 0.0.9 → 0.0.10

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: 859b71a70691f26e28d171883bd5297af8897692
4
- data.tar.gz: a2e612c3c41de11b37a828a4cf8466c01a6682b3
3
+ metadata.gz: dbb778464163c80eb1df8ec60299ce2d2d399ffd
4
+ data.tar.gz: f4e54cf70891ae58da5485e1662fe4233ef41865
5
5
  SHA512:
6
- metadata.gz: 35890438972b2e2898fe20aea2a8e25184b19ff863df5a69208ae4da10d66996cdc50c2b6dff42efb5cd6bb517c02a3b62f2b3cf928baf00564add5b1463ad67
7
- data.tar.gz: 1613d3040d41f1fdbbefbfe4098bf4c901711e1a4eeee76a5d4a651da0f5846e57e8266228c5f04480d6a3c07e9f49dd7967409e2eb78a7dfaa64e334c665b15
6
+ metadata.gz: 45e7ec65e32b08f8bd25834d5fcd513a4514813b5a57780b688dc80f176192df81a458fefd15e66b0f388356ef1e5dd666fb2b4b5657018e3c40e789ce82dbc6
7
+ data.tar.gz: 100912344daee564a5c1770f48cccc16124f2599a42b5edd6fdb574cde320e3211c9cda90be3ffbeeb565825b11327cbb73b53a3de166c33f491c636b2b3528b
data/README.md CHANGED
@@ -66,9 +66,11 @@ end
66
66
  ```
67
67
  In the example above the payload is invalid and as a result `valid` has value
68
68
  `false` and `errors` contains:
69
+
69
70
  ```
70
71
  {"person/name"=>["must be a string"], "person/age"=>["must be an integer"]}
71
72
  ```
73
+
72
74
  Note that invalid elements of the payload are identified by exact path within
73
75
  the payload.
74
76
 
@@ -123,6 +125,7 @@ end
123
125
  The previous examples showed validation of a JSON object. We can also
124
126
  validate JSON arrays. Let's add list of hobbies to our person object from
125
127
  the previous examples:
128
+
126
129
  ```ruby
127
130
  {
128
131
  "person" => {
@@ -132,8 +135,10 @@ the previous examples:
132
135
  }
133
136
  }
134
137
  ```
138
+
135
139
  We will also not accept people with fewer than two hobbies. Validator for this
136
140
  payload:
141
+
137
142
  ```ruby
138
143
  a_hash(
139
144
  allowed_keys("person"),
@@ -145,13 +150,17 @@ a_hash(
145
150
  min_size(2),
146
151
  each(non_empty_string))))))
147
152
  ```
153
+
148
154
  Try to apply this validator to the payload containing invalid list of hobbies
155
+
149
156
  ```
150
157
  ...
151
158
  "hobbies" => ["knitting", {"not" => "allowed"}, "horse riding"]
152
159
  ...
153
160
  ```
161
+
154
162
  and you'll get errors specifying exactly where the invalid element is:
163
+
155
164
  ```
156
165
  {"person/hobbies/1"=>["must be a string"]}
157
166
  ```
@@ -175,9 +184,11 @@ validators](#custom-validators).
175
184
  ### Validators
176
185
 
177
186
  Validator is a function returning boolean value and having following signature:
187
+
178
188
  ```ruby
179
189
  lambda { |validated_object, errors_hash, path| ... }
180
190
  ```
191
+
181
192
  * `errors_hash` is mutated while errors are collected by validators.
182
193
  * `path` represents a path to the invalid element within the JSON object. It is
183
194
  an array of strings (keys in hash map) and integers (indexes of an array).
@@ -209,45 +220,60 @@ be further composed into more powerful validation rules.
209
220
 
210
221
  We want to validate object representing opening hours of a store. E.g. store
211
222
  opened from 9am to 5pm would be represented by
223
+
212
224
  ```ruby
213
225
  {"from" => 9, "to" => 17}
214
226
  ```
227
+
215
228
  Let's start by building validator ensuring that payload is a hash where both
216
229
  `from` and `to` are integers:
230
+
217
231
  ```ruby
218
232
  a_hash(
219
233
  key("from", integer),
220
234
  key("to", integer))
221
235
  ```
236
+
222
237
  We also want to make sure that extra keys like
238
+
223
239
  ```ruby
224
240
  {"from" => 9, "to" => 17, "something" => "wrong"}
225
241
  ```
242
+
226
243
  are not allowed. Let's fix it by using `allowed_keys` validator:
244
+
227
245
  ```ruby
228
246
  a_hash(
229
247
  allowed_keys("from", "to"),
230
248
  key("from", integer),
231
249
  key("to", integer))
232
250
  ```
251
+
233
252
  Better, but we don't want to allow negative hours like this:
253
+
234
254
  ```ruby
235
255
  {"from" => -1, "to" => 17}
236
256
  ```
257
+
237
258
  We can fix it by using more specific integer validator:
259
+
238
260
  ```ruby
239
261
  a_hash(
240
262
  allowed_keys("from", "to"),
241
263
  key("from", non_negative_integer),
242
264
  key("to", non_negative_integer))
243
265
  ```
266
+
244
267
  Let's assume here that we represent store opened all day as
268
+
245
269
  ```ruby
246
270
  {"from" => 0, "to" => 24}
247
271
  ```
272
+
248
273
  so hours greater than 24 should also be invalid. We can validate hour by
249
274
  composing `non_negative_integer` validator with `less_or_equal` using
250
275
  `fail_fast` combinator:
276
+
251
277
  ```ruby
252
278
  hour = fail_fast(non_negative_integer, less_or_equal(24))
253
279
 
@@ -256,17 +282,23 @@ a_hash(
256
282
  key("from", hour),
257
283
  key("to", hour))
258
284
  ```
285
+
259
286
  This validator still has a little problem. Opening hours like this are not
260
287
  rejected:
288
+
261
289
  ```ruby
262
290
  {"from" => 21, "to" => 1}
263
291
  ```
292
+
264
293
  We have to make sure that closing is not before opening. We can do it by using
265
294
  `key_greater_than_key` validator:
295
+
266
296
  ```ruby
267
297
  key_greater_than_key("to", "from")
268
298
  ```
299
+
269
300
  and our validator will look like this:
301
+
270
302
  ```ruby
271
303
  a_hash(
272
304
  allowed_keys("from", "to"),
@@ -274,15 +306,19 @@ a_hash(
274
306
  key("to", hour),
275
307
  key_greater_than_key("to", "from"))
276
308
  ```
309
+
277
310
  That looks good, but it's not complete yet. `a_hash` validator applies all
278
311
  validators to the provided payload by using `run_all` combinator. This
279
312
  behaviour is problematic if our `from` or `to` keys are missing or are not
280
313
  valid integers. Payload
314
+
281
315
  ```ruby
282
316
  {"from" => "abc", "to" => 17}
283
317
  ```
318
+
284
319
  will cause an exception as `key_greater_than_key` can not compare string to
285
320
  integer. Let's fix it by using `fail_fast` and `run_all` combinators:
321
+
286
322
  ```ruby
287
323
  a_hash(
288
324
  allowed_keys("from", "to"),
@@ -292,6 +328,7 @@ a_hash(
292
328
  key("to", hour)),
293
329
  key_greater_than_key("to", "from")))
294
330
  ```
331
+
295
332
  This way if `from` and `to` are not both valid hours we will not be comparing
296
333
  them.
297
334
 
@@ -326,6 +363,7 @@ store = {
326
363
  ```
327
364
 
328
365
  Definition of the store validator (using `from_to` built in the [previous section](#composability)):
366
+
329
367
  ```ruby
330
368
  hour = fail_fast(non_negative_integer, less_or_equal(24))
331
369
 
@@ -359,21 +397,27 @@ store_validator = a_hash(
359
397
 
360
398
  Let's say we try to validate store that has Wednesday opening hours invalid
361
399
  (closing time before opening time) like this:
400
+
362
401
  ```ruby
363
402
  ...
364
403
  "wednesday"=> {"from" => 9, "to" => 7},
365
404
  ...
366
405
  ```
406
+
367
407
  Now we use store validator to fill in the collection of errors using default
368
408
  error messages:
409
+
369
410
  ```ruby
370
411
  errors = {}
371
412
  result = default_errors(store_validator).call(store, errors)
372
413
  ```
414
+
373
415
  Result is `false` and we get validation error in the `errors` hash:
416
+
374
417
  ```ruby
375
418
  {"store/opening_hours/wednesday/to" => ["must be greater than from"]}
376
419
  ```
420
+
377
421
  You can find this example in
378
422
  [functional spec](https://github.com/shutl/composable_validations/blob/master/spec/functional/composable_validations_spec.rb)
379
423
  ready for experiments.
@@ -387,11 +431,13 @@ There are few ways to provide your own error messages.
387
431
  ### Local override
388
432
 
389
433
  You can override error message when building your validator:
434
+
390
435
  ```ruby
391
436
  a_hash(
392
437
  key("from", integer("custom error message")),
393
438
  key("to", integer("another custom error message")))
394
439
  ```
440
+
395
441
  This approach is good if you need just few specialized error messages for
396
442
  different parts of your payload.
397
443
 
@@ -400,6 +446,7 @@ different parts of your payload.
400
446
  If you need to change some of the error messages across all your validators you
401
447
  can provide map of error messages. Keys in the map are symbols matching names
402
448
  of basic validators:
449
+
403
450
  ```ruby
404
451
  error_overrides = {
405
452
  string: "not a string",
@@ -410,9 +457,11 @@ of basic validators:
410
457
  errors_container = ComposableValidations::Errors.new(errors, error_overrides)
411
458
  result = validator.call(valid_data, errors_container, nil)
412
459
  ```
460
+
413
461
  Note that your error messages don't need to be strings. You could for example
414
462
  use rendering function that returns combination of error code, error context
415
463
  and human readable message:
464
+
416
465
  ```ruby
417
466
  error_overrides = {
418
467
  key_greater_than_key: lambda do |validated_object, path, key1, key2|
@@ -428,7 +477,9 @@ and human readable message:
428
477
  errors_container = ComposableValidations::Errors.new(errors, error_overrides)
429
478
  result = validator.call(valid_data, errors_container, nil)
430
479
  ```
480
+
431
481
  And when applied to invalid payload your validator will return an error:
482
+
432
483
  ```ruby
433
484
  {
434
485
  "store/opening_hours/wednesday/to"=>
@@ -441,15 +492,18 @@ And when applied to invalid payload your validator will return an error:
441
492
  ]
442
493
  }
443
494
  ```
495
+
444
496
  You can experiment with this example in the [specs](https://github.com/shutl/composable_validations/blob/master/spec/functional/error_overrides_spec.rb#L19).
445
497
 
446
498
  ### Override error container
447
499
 
448
500
  You can override error container class and provide any error collecting
449
501
  behaviour you need. The only method error container must provide is:
502
+
450
503
  ```ruby
451
504
  def add(msg, path, object)
452
505
  ```
506
+
453
507
  where
454
508
  * `msg` is a symbol of an error or an array where first element is a symbol of
455
509
  error and remaining elements are context needed to render the error message.
@@ -458,6 +512,7 @@ error and remaining elements are context needed to render the error message.
458
512
  * `object` is a validated object.
459
513
 
460
514
  Example of error container that just collects error paths:
515
+
461
516
  ```ruby
462
517
  class CollectPaths
463
518
  attr_reader :paths
@@ -475,18 +530,23 @@ validator = ...
475
530
  errors_container = CollectPaths.new
476
531
  result = validator.call(valid_data, errors_container, nil)
477
532
  ```
533
+
478
534
  and example of the value of `errors_container.paths` after getting an error:
535
+
479
536
  ```ruby
480
537
  [["store", "opening_hours", "wednesday", "to"]]
481
538
  ```
539
+
482
540
  You can experiment with this example in the [spec](https://github.com/shutl/composable_validations/blob/master/spec/functional/error_overrides_spec.rb#L80).
483
541
 
484
542
  ## Custom validators
485
543
 
486
544
  You can create your own validators as functions returning lambdas with signature
545
+
487
546
  ```ruby
488
547
  lambda { |validated_object, errors_hash, path| ... }
489
548
  ```
549
+
490
550
  Use `error` helper function to add errors to the error container and
491
551
  functions `validate`, `precheck` and `nil_or` to avoid boilerplate.
492
552
 
@@ -494,10 +554,13 @@ functions `validate`, `precheck` and `nil_or` to avoid boilerplate.
494
554
 
495
555
  Let's say we have an ActiveRecord model Store and API allowing update of the
496
556
  store name. We will be receiving payload:
557
+
497
558
  ```ruby
498
559
  { name: 'new store name' }
499
560
  ```
561
+
500
562
  We can build validator ensuring uniqueness of the store name:
563
+
501
564
  ```ruby
502
565
  a_hash(
503
566
  allowed_keys('name'),
@@ -505,7 +568,9 @@ a_hash(
505
568
  non_empty_string,
506
569
  unique_store_name))
507
570
  ```
571
+
508
572
  where `unique_store_name` is defined as:
573
+
509
574
  ```ruby
510
575
  def unique_store_name
511
576
  lambda do |store_name, errors, path|
@@ -517,7 +582,9 @@ def unique_store_name
517
582
  end
518
583
  end
519
584
  ```
585
+
520
586
  Note that we could simplify this code by using `validate` helper method:
587
+
521
588
  ```ruby
522
589
  def unique_store_name
523
590
  validate("has already been taken") do |store_name|
@@ -525,8 +592,10 @@ def unique_store_name
525
592
  end
526
593
  end
527
594
  ```
595
+
528
596
  We could also generalize this function and end up with generic ActiveModel
529
597
  attribute uniqueness validator ready to be reused:
598
+
530
599
  ```ruby
531
600
  def unique(klass, attr_name)
532
601
  validate("has already been taken") do |attr_value|
@@ -565,12 +634,14 @@ a_hash(
565
634
  default implementation of the error collection object. Returned function is
566
635
  not a composable validator so it should only be applied to the top level
567
636
  validator right before applying it to the object. Example:
637
+
568
638
  ```ruby
569
639
  errors = {}
570
640
  default_errors(validator).call(validated_object, errors)
571
641
  ```
572
642
 
573
643
  * `each_in_slice(range, validator)` - applies `validator` to each slice of the array. Example:
644
+
574
645
  ```ruby
575
646
  array(
576
647
  each_in_slice(0..-2, normal_element_validator),
@@ -699,6 +770,7 @@ a_hash(
699
770
  returns `true` if `&blk` returns `true` or applies all `validators` using
700
771
  `run_all` combinator if `&blk` returns `false`. Example - validate that value
701
772
  is a number but also allow value "infinity":
773
+
702
774
  ```ruby
703
775
  precheck(float) { |v| v == 'infinity' }
704
776
  ```
@@ -730,6 +802,8 @@ a_hash(
730
802
  returns `true` if `&blk` returns `true` and `false` otherwise. `msg` is an
731
803
  error message added to the error container when validation returns `false`.
732
804
  Example - ensure that validated object is equal "hello":
805
+
733
806
  ```ruby
734
807
  validate('must be "hello"') { |v| v == 'hello' }
735
808
  ```
809
+
@@ -1,3 +1,3 @@
1
1
  module ComposableValidations
2
- VERSION = '0.0.9'
2
+ VERSION = '0.0.10'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composable_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kajetan Bojko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-09 00:00:00.000000000 Z
11
+ date: 2016-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec